The SPAN Panel integration consists of three repositories:
| Repo | Purpose | Branch |
|---|---|---|
span (this repo) |
HA custom integration (Python) | main |
span-panel-api |
API client library (Python) | main |
span-card |
Frontend dashboard (JavaScript) | integration-panel |
The card repo produces two JS bundles:
span-panel-card.js-- Lovelace card (standalone, HACS-distributable)span-panel.js-- Full-page sidebar panel (served by the integration)
Both bundles are committed as build artifacts in custom_components/span_panel/frontend/dist/.
- uv for Python dependency management
- prek for pre-commit hooks (fast, Rust-based)
- Python 3.14.2+
- Node.js 18+ and npm (for the card repo)
- Home Assistant Core for local development
- direnv (recommended, for automatic env setup)
# Clone all repos
git clone <span-repo-url> ~/projects/HA/span
git clone <span-panel-api-url> ~/projects/HA/span-panel-api
git clone <span-card-repo-url> ~/projects/HA/cards/span-card
# Set up the integration
cd ~/projects/HA/span
uv sync
prek install
# Set up the API library
cd ~/projects/HA/span-panel-api
uv sync
# Set up the card
cd ~/projects/HA/cards/span-card
npm installThe .env file configures paths to sibling repos and the local HA config directory. These variables are used by build scripts.
cd ~/projects/HA/span
cp .env.example .envThe defaults assume the standard workspace layout:
# Path to span-panel-api repo (for editable pip install)
export SPAN_PANEL_API_DIR=../span-panel-api
# Path to span-card frontend repo (for build-frontend.sh)
export SPAN_CARD_DIR=../cards/span-card
# Path to HA config directory
export HA_CONFIG_DIR=./ha-configVS Code loads .env automatically into Python terminals (requires python.terminal.useEnvFile enabled in workspace settings). For shell use outside VS Code,
direnv is recommended -- create an .envrc that sources .env:
echo 'dotenv' > .envrc
direnv allowThis project uses prek for pre-commit hooks. Hooks run automatically on git commit and check formatting, linting, type checking, translations, and test
coverage.
The linters may modify files (e.g., to sort imports or reformat). Files that are changed or fail checks will be unstaged. Review the changes, re-stage, and recommit.
To run hooks manually:
# All hooks on staged files
prek run
# All hooks on all files
prek run --all-filesYou can also use VS Code's Tasks: Run Task from the command palette to run Run all Pre-commit checks.
The span-card repo is independent -- it has its own git history, branches, and releases. The integration repo consumes its build output via a copy script. There is no git submodule.
# 1. Make changes in the span-card repo
cd ~/projects/HA/cards/span-card
# ... edit files ...
# 2. Build and copy into the integration
cd ~/projects/HA/span
./scripts/build-frontend.sh
# 3. Commit both repos
cd ~/projects/HA/cards/span-card
git add -A && git commit -m "feat: description of card changes"
cd ~/projects/HA/span
git add custom_components/span_panel/frontend/dist/
git commit -m "feat: update frontend with card changes"scripts/build-frontend.sh does three things:
- Runs
npm run buildin the span-card repo (rollup produces two IIFE bundles) - Copies
dist/span-panel.jsanddist/span-panel-card.jsintocustom_components/span_panel/frontend/dist/ - Prints the files and a reminder to stage them
The script reads SPAN_CARD_DIR from .env (or the environment). You can also pass the path as an argument:
# Uses SPAN_CARD_DIR from .env
./scripts/build-frontend.sh
# Via argument (overrides env var)
./scripts/build-frontend.sh ~/projects/HA/cards/span-cardWhen running HA Core locally, the integration is symlinked into config/custom_components/span_panel. The frontend JS files are served from
custom_components/span_panel/frontend/dist/ via the async_register_static_paths call in __init__.py.
After rebuilding the frontend, restart HA to pick up the new JS. Browsers cache aggressively -- a hard refresh (Cmd+Shift+R) of the panel page also works if you
clear the cache_headers flag during development.
# Full suite
python -m pytest tests/ -q
# Single file
python -m pytest tests/test_current_monitor.py -q
# With coverage
python -m pytest tests/ --cov=custom_components/span_panel --cov-report=term-missing# Ruff (lint + format)
ruff check custom_components/span_panel/
ruff format custom_components/span_panel/
# Mypy
python -m mypy custom_components/span_panel/
# Markdown
./scripts/fix-markdown.sh .Source strings live in custom_components/span_panel/strings.json. Translated files in translations/ are synced from strings.json by the pre-commit hook
(sync_translations.py).
To add a new translatable string:
- Add the key to
strings.json - Add translations to each
translations/<lang>.json - The pre-commit hook validates that all translation files match
strings.jsonkeys
The integration registers a sidebar panel in async_setup() (domain-level, called once). Panel visibility (show_panel, admin_only) is stored in
domain-level storage (span_panel_settings) -- shared across all config entries. These settings are editable from any entry's options flow.
See .vscode/settings.json.example for starter settings.