You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Pacing-transparency UI on the control card (UX #4 / #6 / #8) — four new data-surface attributes on sensor.foxess_smart_operations (discharge_deferred_reason + charge_deferred_reason, discharge_safety_floor_w + discharge_peak_consumption_kw + discharge_paced_target_w, discharge_grid_export_limit_w + discharge_clamp_active) are rendered directly as rows on foxess-control-card:
Deferred reason (UX #4) — both charge and discharge sections render a wide row with the explanatory text produced by _explain_*_deferral() whenever the deferred phase is active. Users finally see why pacing is holding back (e.g. "Feed-in limit reached — holding until more solar is available", "Export clamp slack 5.5 kW exceeds projected peak 3.0 kW; single headroom applied"). Longer strings wrap via new .detail-row-wide / .detail-value-wrap styling.
Safety floor (UX #6) — a safety_floor row appears whenever discharge_safety_floor_w > 0. An upward-arrow icon (mdi:arrow-up-bold) surfaces when the paced target is below the floor (the C-001 floor is actively raising paced power). The row is click-expandable: tapping it reveals a translation-aware explainer with the actual peak value interpolated (e.g. "Minimum discharge power. Computed as peak household load (1.0 kW) × 1.5, so the battery can cover sudden load spikes without pulling from the grid."). Mobile-friendly — no hover needed.
Export clamp split (UX #8) — when grid_export_limit is configured, the discharge power row splits into an inverter / export pair separated by /. The export side takes the warning colour and shows a mdi:fence icon when discharge_clamp_active is true, so the user can see immediately that hardware clamping is capping their export. Unchanged for sites without an export limit.
Standalone foxess-taper-card (UX #5) rendering the BMS acceptance-ratio histogram from the new taper_profile attribute on sensor.foxess_smart_operations (both charge and discharge histograms as chart-friendly {soc, ratio, count} lists). The card shows per-SoC-bin horizontal bars with observation count annotation and a low-confidence marker (·) for bins with fewer than 3 observations. Users opt in by adding type: custom:foxess-taper-card to their dashboard; an ApexCharts variant is also covered as a user template in docs/lovelace-examples.md.
Card translation coverage test (tests/test_card_translations.py): parses each card's TRANSLATIONS table and asserts every non-English locale carries every English key. Structurally prevents the class of bug that left the info_log sensor ID broken in beta.7 — a locale missing a key silently falls back to the raw key name.
E2E card-wiring tests: 7 new TestControlCard tests (split power row, clamp-active toggle, safety-floor row + expandable explainer, charge/discharge deferred-reason rows, charge section title phase distinction) and 3 new TestTaperCard tests (card mount, empty state, seeded-profile bar widths). Taper card added to the E2E Lovelace dashboard.
D-051 "Transparency attributes surfaced via card rows, not tooltips" captures the design decision — visible rows over hover tooltips / debug panels / consolidated toggles — in docs/knowledge/04-design/lovelace-cards.md, tracing to C-020 and C-034.
Fixed
Structured events silently dropped when a user set a per-module log level: emit_event used logger.info(message, extra=...), which Python's logging framework evaluates against Logger.isEnabledFor()before propagation to ancestor handlers. On a live HA (v1.0.12) with a logger: YAML config pinning custom_components.foxess_control.foxess.inverter above INFO, every SCHEDULE_WRITE event emitted from Inverter._post_schedule() was dropped at the child's level check — never reaching the debug-log sensor attached to the parent custom_components.foxess_control logger. Fixed by changing emit_event to build the LogRecord via logger.makeRecord() and dispatch via logger.handle(), bypassing isEnabledFor() while still running the logger's filter chain and every handler's level/filter chain. Structured events are telemetry the integration emits on its own behalf; visibility is controlled at the handler level, not at the logger level. Applies to all six event types (ALGO_DECISION, TICK_SNAPSHOT, SCHEDULE_WRITE, TAPER_UPDATE, SERVICE_CALL, SESSION_TRANSITION). Three-test regression suite TestInverterScheduleWriteReachesParentHandler covers the exact production symptom (child at WARNING) + default-levels baseline + session-context-survives-child-override neighbourhood.
Page-fixture flake in E2E CI caused by HA's housekeeping navigation (Flaky Test Detection surfaced this on four consecutive tag runs v1.0.11–v1.0.13-beta.2; live-diagnosed by observing a real HA container). Two compounding bugs in the staged Lovelace-panel wait (tests/e2e/conftest.py): (a) the final stage's predicate was a bare !!panel attach check, which could return truthy moments before HA's service-worker/auth-refresh navigation at t≈12s detached the panel — surfacing either as a 30s wait_for_function timeout or, in the test body, as Locator.screenshot: Element is not attached to the DOM; (b) _wait_for_stage re-capped every retry at max_stage_ms=30000 even when 50+ seconds of overall budget remained, so a post-navigation rebuild under slow-shard CPU contention had insufficient time to complete. Stage-3 predicate now requires three settled signals (hass.connected === true, ha-panel-lovelace mounted, panel.hass wired) — proving the panel is past HA's initial navigation churn, not merely attached; and a new _LOVELACE_STAGE_TIMEOUTS_MS mapping lets the final stage use the full remaining overall budget on retries while keeping the tight 30s cap on stages 1+2 (shadow-root attachment is synchronous and catastrophic failures must still time out fast).
SoakRecorder dropped invariant-violation detail when persisting run results: save_run in tests/soak/results_db.py wrote a violations count to the runs row but never inserted corresponding events rows, so the rule name and detail message were lost. Observed on a 2026-04-23 nightly when test_charge_solar_then_spike recorded violations=3 with zero persisted detail — making post-mortem diagnosis impossible and hollowing out the C-020 observability value of the recorder. Each InvariantViolation now emits an event_type='violation' row with {rule}: {detail} as the event detail, anchored to the nearest sample's SoC/power/state so cross-run queries (SELECT * FROM events WHERE event_type='violation') can correlate violations back to system state.