Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
520ebb7
feat: current mode with NEC breaker limit lines and panel amperage
cayossarian Mar 10, 2026
1a96aa8
refactor: extract grid renderer to core module
cayossarian Mar 30, 2026
26e6735
refactor: extract sub-device renderer to core module
cayossarian Mar 30, 2026
ba2d750
refactor: extract history loader to core module
cayossarian Mar 30, 2026
8d5fd34
refactor: extract DOM updater to core module
cayossarian Mar 30, 2026
8421c58
refactor: fix code review issues in core module extraction
cayossarian Mar 30, 2026
df8f877
refactor: extract header renderer to core module with new stats
cayossarian Mar 30, 2026
f44bb24
feat: add monitoring status fetcher and helpers
cayossarian Mar 30, 2026
27f9f2c
feat: add shedding icons, monitoring indicators, and gear icons to ci…
cayossarian Mar 30, 2026
bfb9104
feat: add A/W toggle switching all values and chart axes
cayossarian Mar 30, 2026
e135ea2
feat: add side panel web component for circuit and panel config
cayossarian Mar 30, 2026
55bb453
feat: wire gear icon clicks to side panel in card
cayossarian Mar 30, 2026
af1e255
feat: integrate monitoring status cache into card lifecycle
cayossarian Mar 30, 2026
128a7e3
feat: add integration panel shell with tab router and multi-panel sel…
cayossarian Mar 30, 2026
cf95269
feat: implement dashboard tab reusing core rendering modules
cayossarian Mar 30, 2026
5401a1b
feat: implement monitoring tab with overrides table
cayossarian Mar 30, 2026
10df0db
feat: implement settings tab with integration link
cayossarian Mar 30, 2026
3fc8b50
feat: add monitoring summary bar between header and circuit grid
cayossarian Mar 30, 2026
391dc01
fix: hide header stats when corresponding entities don't exist
cayossarian Mar 30, 2026
80ddb44
fix: add missing threshold fields and error display to side panel
cayossarian Mar 30, 2026
f6fdd73
feat: add global monitoring settings section and panel config link
cayossarian Mar 30, 2026
1e6701e
feat: use topology panel_entities instead of pattern matching
cayossarian Mar 30, 2026
5a65dde
chore: track dist/ for HACS and submodule consumers
cayossarian Mar 30, 2026
172231a
fix: panel dashboard smoke test fixes
cayossarian Mar 30, 2026
cbdb5df
fix: monitoring enable/disable toggle and service call
cayossarian Mar 30, 2026
61233c7
feat: monitoring tab UX improvements
cayossarian Mar 30, 2026
d64566e
fix: sidebar monitoring toggle and monitoring tab improvements
cayossarian Mar 30, 2026
74ceee7
fix: re-render monitoring tab after saving global settings
cayossarian Mar 30, 2026
8f99b1b
feat: multi-panel monitoring and cooldown column
cayossarian Mar 30, 2026
48e397e
fix: sidebar cooldown editable and grid state translation
cayossarian Mar 30, 2026
985b9e1
feat: add notification settings to monitoring tab
cayossarian Mar 30, 2026
dc6bcdb
feat: add i18n support with translations for en, es, fr, ja, pt
cayossarian Mar 30, 2026
04a7545
chore: bump version to 0.9.0 and update changelog
cayossarian Mar 30, 2026
f1942f6
fix: clear chart history and reload on W/A metric toggle
cayossarian Mar 31, 2026
c68c188
feat: add GRAPH_HORIZONS constants, GraphSettingsCache, and i18n keys
cayossarian Mar 31, 2026
a10eddf
feat: expand Settings tab with global + per-circuit graph horizon con…
cayossarian Mar 31, 2026
8884942
feat: add graph horizon section to side panel with cache sync
cayossarian Mar 31, 2026
3c6cf4c
feat: adaptive history loading with per-circuit horizons and recorder…
cayossarian Mar 31, 2026
a1d0411
feat: scale chart x-axis per circuit horizon
cayossarian Mar 31, 2026
b2f3ee6
feat: synchronize graph settings between Settings tab and side panel
cayossarian Mar 31, 2026
57c35ef
feat: complete graph time horizon frontend support
cayossarian Mar 31, 2026
a60441a
fix: add missing graph horizon support to standalone card
cayossarian Mar 31, 2026
8e56d2b
fix: show decimal precision on Y-axis for sub-watt power values
cayossarian Mar 31, 2026
808a20e
fix: use decimal Y-axis labels for small values on both power and cur…
cayossarian Mar 31, 2026
8e82566
fix: convert statistics timestamps from seconds to milliseconds
cayossarian Mar 31, 2026
73d6a3f
feat: add 1-week graph time horizon scale
cayossarian Mar 31, 2026
c3be98d
chore: rebuild dist with timestamp fix, decimal axis, and 1-week scale
cayossarian Mar 31, 2026
23f251b
fix: remove double millisecond conversion on statistics timestamps
cayossarian Mar 31, 2026
9df4e03
feat: add always-on and SoC composite shedding priority icons
cayossarian Mar 31, 2026
def81ef
feat: replace graph time horizon radio buttons with segmented button bar
cayossarian Mar 31, 2026
49abd00
feat: add shedding priority legend to panel header
cayossarian Mar 31, 2026
5adfb49
Add lifecycle visibility handling for panel and card
cayossarian Apr 1, 2026
457494c
feat: use HA native app layout with ha-menu-button for sidebar integr…
cayossarian Apr 1, 2026
9f07a9a
fix: prevent blank charts on recorder refresh and duplicate element r…
cayossarian Apr 1, 2026
45779a8
feat: add slide-to-confirm safety switch for circuit toggles
cayossarian Apr 1, 2026
ba3aefe
fix: use ResizeObserver for responsive layout instead of viewport med…
cayossarian Apr 1, 2026
c1c249f
fix: replace ResizeObserver with CSS flex-wrap for responsive header
cayossarian Apr 2, 2026
3cf54d3
feat: replace panel gear placeholder with full graph settings UI
cayossarian Apr 2, 2026
5240101
fix: remove 900px max-width cap so frontend panel fills browser width
cayossarian Apr 2, 2026
2042e01
fix: add 4:1 aspect ratio to chart containers for proportional scaling
cayossarian Apr 2, 2026
cb6b1b8
feat: move sub-devices above circuits with 2-column EVSE grid layout
cayossarian Apr 2, 2026
1c4e262
feat: add getEffectiveSubDeviceHorizon to graph settings
cayossarian Apr 2, 2026
31396b0
feat: group sub-device history loading by per-device horizon
cayossarian Apr 2, 2026
6c7c7f3
feat: use per-sub-device horizons in chart rendering
cayossarian Apr 2, 2026
a0d24e0
feat: add gear icon to sub-device headers
cayossarian Apr 2, 2026
aeacdbd
feat: add sub-device mode to side panel with horizon bar
cayossarian Apr 2, 2026
4b78bdc
feat: wire sub-device horizon map through card lifecycle
cayossarian Apr 2, 2026
1674a7b
feat: wire sub-device horizon map through panel dashboard
cayossarian Apr 2, 2026
70f152f
feat: update dist with sub-device horizon support
cayossarian Apr 2, 2026
98b02f3
update image for card config
cayossarian Apr 2, 2026
fb8a1a4
fix: multi-panel dropdown always visible with styled select
cayossarian Apr 2, 2026
4161d91
fix: resize sub-device charts when browser window changes width
cayossarian Apr 2, 2026
d62f62c
fix: move gear and slider inline with panel identity row
cayossarian Apr 2, 2026
7989f25
chore: update dist with header layout and chart aspect-ratio fixes
cayossarian Apr 2, 2026
6606a2d
update for unecessary usage of HACs
cayossarian Apr 2, 2026
b442dc7
chore: remove HACS manifest and CI validation
cayossarian Apr 2, 2026
a24acee
fix: address all 10 copilot review comments on PR #3
cayossarian Apr 2, 2026
c1bbea2
refactor: migrate to strict TypeScript, extract DashboardController, …
cayossarian Apr 2, 2026
a98d1bb
fix: align shedding priority labels with SPAN plain English
cayossarian Apr 2, 2026
c25ffcb
fix: listener leaks, stale controller state, tooltip units, and thres…
cayossarian Apr 2, 2026
a5d7fc7
fix: refresh sub-device non-realtime history in refreshRecorderData
cayossarian Apr 2, 2026
f8d6db6
fix: remove return_response from void service calls in side panel
cayossarian Apr 2, 2026
c8ec02c
feat: add visual preview with mini charts to card picker
cayossarian Apr 2, 2026
bf33213
fix: hide monitoring toggle in card side panel
cayossarian Apr 2, 2026
309802b
fix: address remaining Copilot review feedback
cayossarian Apr 2, 2026
2ac8c9c
build: rebuild dist bundles
cayossarian Apr 2, 2026
7506b7f
fix: prevent chart x-axis clipping on narrow viewports
cayossarian Apr 3, 2026
e91cb23
fix: guard custom element registration against scoped registry duplic…
cayossarian Apr 3, 2026
2f643d8
fix: address Copilot review feedback for XSS, monitoring key consiste…
cayossarian Apr 3, 2026
c982deb
refactor: unify notification targets into single dropdown with All to…
cayossarian Apr 3, 2026
9e4ff1f
fix: rebuild panel shell when DOM is lost after browser backgrounding
cayossarian Apr 3, 2026
27cbd5f
fix: address Copilot review feedback for data guards, display consist…
cayossarian Apr 3, 2026
b643dd0
Merge branch 'main' into integration-panel
cayossarian Apr 3, 2026
a8bd3ab
fix: ignore GitHub auto-generated instructions in prettier checks
cayossarian Apr 3, 2026
6dc8dde
fix: ignore GitHub auto-generated instructions in markdownlint checks
cayossarian Apr 3, 2026
3797a2e
fix: format Copilot review instructions and revert lint ignores
cayossarian Apr 3, 2026
6948d77
fix: include .github markdown files in lint and pre-commit checks
cayossarian Apr 3, 2026
fa512aa
feat: add {local_time} template variable to notification placeholders
cayossarian Apr 3, 2026
c660330
fix: use has_override flag for monitoring mode detection in side panel
cayossarian Apr 3, 2026
5a88bbc
fix: keep side panel graph horizon in sync with settings changes
cayossarian Apr 3, 2026
2b730f7
Revert "fix: keep side panel graph horizon in sync with settings chan…
cayossarian Apr 3, 2026
c9032e6
fix: retry panel render on visibility restore after WS reconnect
cayossarian Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/instructions/*.instructions.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# Copilot Review Instructions

- Before reviewing a PR examine any previous comments on the PR and the resolutions.
- Use those previous commments as the basis for the new review so that resolutions that were previously resolved and where code changes have not diverged are not repeated unecessarily
- Use those previous comments as the basis for the new review so that resolutions that were previously resolved and where code changes have not diverged are not
repeated unnecessarily.
13 changes: 3 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ jobs:

- run: npm ci
- run: npx eslint src/
- run: npx prettier --check "**/*.{js,mjs,json,md,yml,yaml}"
- run: npx prettier --check "**/*.{ts,js,mjs,json,md,yml,yaml}"
- run: npx tsc --noEmit
- run: npx vitest run
- run: npx markdownlint-cli2 "**/*.md"

build:
Expand All @@ -34,12 +36,3 @@ jobs:

- run: npm ci
- run: npm run build

hacs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: hacs/action@main
with:
category: plugin
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# Dependencies
node_modules/

# Build output
dist/
# Build output (dist/ is tracked for HACS and submodule consumers)

# Claude
.claude
Expand Down
3 changes: 2 additions & 1 deletion .markdownlint-cli2.jsonc
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"globs": [
"*.md",
"docs/**/*.md"
"docs/**/*.md",
".github/**/*.md"
],
"ignores": [
".venv/**",
Expand Down
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Changelog

## 0.9.0

- Add integration panel with tab router and multi-panel selector
- Add monitoring tab with overrides table, summary bar, and notification settings
- Add settings tab with integration link and global monitoring configuration
- Add side panel for circuit and panel configuration
- Add A/W toggle switching all values and chart axes
- Add shedding icons, monitoring indicators, and gear icons to circuit cells
- Add i18n support with translations for en, es, fr, ja, pt
- Use topology panel_entities instead of pattern matching

## 0.8.9

- Show amps instead of watts above circuit graphs when chart metric is set to `current`
- Fix Y-axis scale to 0–125% of breaker rating in current mode
- Add red horizontal line at 100% of breaker rating (NEC trip point)
- Add yellow dashed line at 80% of breaker rating (NEC continuous load limit)
- Add total current (amps) stat to panel header

## 0.8.8

- Chart Y-axis formatting uses absolute values for power metric

## 0.8.7

- Use dynamic `panel_size` from topology instead of hardcoded 32

## 0.8.6

- Fix editor storing default 5 minutes when user only changes days/hours
- Use statistics API for long-duration charts
- Fix 0-minute config bug

## 0.8.5

- Add project tooling, CI, and HACS support

## 0.8.4

- Initial SPAN Panel custom Lovelace card
221 changes: 221 additions & 0 deletions DEVELOPER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
# Developer Guide

## Prerequisites

- Node.js 20+
- npm

## Setup

```bash
npm install
```

This installs all dev dependencies and sets up lefthook pre-commit hooks.

## Scripts

| Command | Description |
| -------------------- | -------------------------------------------------- |
| `npm run build` | Type-check and produce minified production bundles |
| `npm run dev` | Watch mode with hot-reload (no minification) |
| `npm run typecheck` | Type-check only (no output) |
| `npm run lint` | Run ESLint on `src/` |
| `npm test` | Run test suite |
| `npm run test:watch` | Run tests in watch mode |

## Project Structure

```text
src/
types.ts # Shared TypeScript interfaces
constants.ts # Global constants, metric definitions, named magic numbers
i18n.ts # Internationalization (en, es, fr, ja, pt)
index.ts # Card entry point — registers custom elements

card/
span-panel-card.ts # Main Lovelace card (extends HTMLElement)
card-discovery.ts # WebSocket topology discovery + entity fallback
card-styles.ts # Card CSS

core/
dashboard-controller.ts # Shared controller (used by card + panel dashboard)
grid-renderer.ts # Breaker grid HTML builder
header-renderer.ts # Panel header HTML builder
sub-device-renderer.ts # BESS/EVSE sub-device HTML builder
dom-updater.ts # Incremental DOM updates for live data
history-loader.ts # Loads history from HA recorder (raw + statistics)
monitoring-status.ts # Monitoring status cache + utilization helpers
graph-settings.ts # Graph horizon settings cache + effective horizon lookup
side-panel.ts # Sliding configuration panel (custom element)

panel/
index.ts # Integration panel entry point
span-panel.ts # Panel container with tab navigation
tab-dashboard.ts # Dashboard tab (delegates to DashboardController)
tab-monitoring.ts # Monitoring configuration tab
tab-settings.ts # Settings tab (graph horizons, integration link)

editor/
span-panel-card-editor.ts # Visual card editor for Lovelace UI

chart/
chart-options.ts # ECharts configuration builder
chart-update.ts # Chart DOM creation/update

helpers/
sanitize.ts # HTML escaping (XSS prevention)
format.ts # Power/unit formatting
layout.ts # Tab-to-grid position calculations
chart.ts # Chart metric selection
history.ts # History duration, sampling, deduplication
entity-finder.ts # Sub-device entity discovery by name/suffix

tests/ # Unit tests (vitest)
scripts/
validate-i18n.mjs # Validates translation key consistency across languages
fix-markdown.sh # Markdown formatting helper
dist/
span-panel-card.js # Card bundle (IIFE, minified)
span-panel.js # Integration panel bundle (IIFE, minified)
```

## Architecture

The project produces two independent bundles from two entry points:

1. **`src/index.ts`** → `dist/span-panel-card.js` — the Lovelace custom card
2. **`src/panel/index.ts`** → `dist/span-panel.js` — the integration panel

Both share the same core modules. The `DashboardController` in `src/core/dashboard-controller.ts` encapsulates all shared dashboard behavior (live sampling,
history loading, horizon maps, slide-to-confirm, toggle/gear clicks, resize observation) so the card and panel dashboard tab delegate to it rather than
duplicating logic.

### Data Flow

```text
Home Assistant
├─ Device Registry → panel discovery
├─ Entity Registry → entity fallback discovery
├─ WebSocket API → span_panel/panel_topology
├─ Recorder (history) → raw history + statistics
└─ hass.states → live entity state
DashboardController
├─ recordSamples() → live power sampling (1s interval)
├─ refreshRecorderData() → periodic recorder refresh (30s)
├─ buildHorizonMaps() → per-circuit/sub-device graph horizons
└─ updateDOM() → incremental DOM updates
Renderers (grid, header, sub-device, chart)
```

### Key Types

All shared types live in `src/types.ts`:

- `HomeAssistant` — subset of the HA frontend type used by this project
- `PanelTopology` — circuits, sub-devices, panel entities
- `Circuit` — name, tabs, entities, breaker rating, shedding info
- `CardConfig` — user-facing card configuration
- `HistoryMap` — `Map<string, HistoryPoint[]>` for power/current history
- `ChartMetricDef` — defines how a metric is formatted and displayed
- `GraphSettings` — per-circuit and global graph horizon overrides
- `MonitoringStatus` — utilization, alerts, thresholds

## TypeScript

The project uses strict TypeScript with these compiler options:

- `strict: true`
- `noUncheckedIndexedAccess: true` — forces guarding Record/array index access
- `noUnusedLocals: true`
- `noUnusedParameters: true`
- Target: ES2020, bundled as IIFE by Rollup

Type-checking runs before every build (`tsc --noEmit && rollup -c`) and in the pre-commit hook.

## Build

Rollup bundles each entry point into a single IIFE file:

- **Production** (`npm run build`): TypeScript plugin compiles `.ts`, then Terser minifies
- **Development** (`npm run dev`): TypeScript plugin compiles `.ts`, no minification, watch mode

The TypeScript plugin handles compilation; `tsc --noEmit` is used only for type-checking. No `.js` output is produced by `tsc` directly.

## Linting

ESLint uses the flat config format with `typescript-eslint`:

- `eslint:recommended` + `tseslint.configs.recommended`
- `eqeqeq` (strict equality)
- `no-var`, `prefer-const`
- `@typescript-eslint/no-shadow`
- `consistent-return`
- Unused vars with `argsIgnorePattern: ^_`

Prettier handles formatting (160 char width, 2-space indent, double quotes).

## Testing

Tests use [Vitest](https://vitest.dev/) and live in `tests/`. They cover all pure helper functions and core utilities:

```bash
npm test # single run
npm run test:watch # watch mode
```

To add a test, create `tests/<module>.test.ts` and import from `../src/<path>.js` (TypeScript resolves `.js` imports to `.ts` files).

## Pre-commit Hooks

Lefthook runs these checks in parallel on staged files:

| Hook | Glob | Command |
| ------------- | -------------------------------- | -------------------------------- |
| prettier | `*.{ts,js,mjs,json,md,yml,yaml}` | `prettier --check` |
| eslint | `*.{ts,js,mjs}` | `eslint` |
| typecheck | `src/**/*.ts` | `tsc --noEmit` |
| markdownlint | `*.md` | `markdownlint-cli2` |
| i18n-validate | `src/**/*.ts` | `node scripts/validate-i18n.mjs` |

## CI

GitHub Actions (`.github/workflows/ci.yml`) runs on push/PR to `main`:

- **lint job**: ESLint, Prettier, TypeScript type-check, Vitest, markdownlint
- **build job**: Full production build (`npm run build`)

## Internationalization

Translations live in `src/i18n.ts` with 5 languages: `en`, `es`, `fr`, `ja`, `pt`.

The `scripts/validate-i18n.mjs` script checks:

1. Every `t("key")` call in source has a matching English key
2. Every English key exists in all other languages
3. No orphaned keys in non-English languages

Run manually: `node scripts/validate-i18n.mjs`

## Adding a New Translation Key

1. Add the key to the `en` block in `src/i18n.ts`
2. Add translations for `es`, `fr`, `ja`, `pt`
3. Run `node scripts/validate-i18n.mjs` to verify

## Distribution

The SPAN Panel integration serves the card automatically from its static path. No HACS or manual installation is required. The integration registers both
`dist/span-panel-card.js` and `dist/span-panel.js` as Lovelace resources.

To update the distributed files after changes:

```bash
npm run build
```

Then commit the updated `dist/` files.
33 changes: 13 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,23 @@ positions.

## Requirements

- [SPAN Panel integration](https://github.com/SpanPanel/span) installed and configured
- [SPAN Panel integration](https://github.com/SpanPanel/span) **v2.0.5 or later** installed and configured
- Circuits must have `tabs` attributes (included in SPAN Panel integration v1.2+)

## Installation

### HACS (Custom Repository)

1. Open HACS in Home Assistant
2. Click the three-dot menu in the top right and select **Custom repositories**
3. Add the repository URL and select **Dashboard** as the category
4. Click **Add**
5. Search for "SPAN Panel Card" in HACS and click **Install**
6. Restart Home Assistant

### Manual

1. Download `span-panel-card.js` from the [latest release](../../releases/latest)
2. Copy the file to your `config/www/` directory
3. Add the resource in Home Assistant:
- Go to **Settings > Dashboards > Resources**
- Click **Add Resource**
- URL: `/local/span-panel-card.js`
- Type: **JavaScript Module**
4. Refresh your browser
As of SPAN Panel integration **v2.0.5**, this card is automatically registered as a Lovelace resource by the integration itself — no separate installation is
required. The integration serves the card JS from its own static path and writes the resource entry on setup.

> **Migrating from HACS or manual install?** If you previously installed this card via HACS or manually copied `span-panel-card.js` to your `www/` directory,
> you should:
>
> 1. Remove the HACS-managed dashboard resource (HACS > Dashboard > SPAN Panel Card > Remove) — or delete the manual `/local/span-panel-card.js` resource from
> **Settings > Dashboards > Resources**
> 2. Remove `span-panel-card.js` from your `config/www/` directory if present
> 3. Restart Home Assistant
>
> The integration will register its own copy of the card automatically on next startup.

## Configuration

Expand Down
1 change: 1 addition & 0 deletions dist/span-panel-card.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/span-panel.js

Large diffs are not rendered by default.

Loading
Loading