Contributing¶
Everything Presence Pro Grid is four subsystems (Python integration, TypeScript/Lit panel, ESPHome firmware, MkDocs docs) in one repo. Contributing usually means touching one or two of them. This page covers the shared dev-environment setup, how to run the tests the pre-push hook enforces, and the PR process.
Prerequisites¶
- Python 3.13+ — matches the integration's
requires-pythonfloor. - Node.js (LTS) and
npm— for the frontend build and tests. - CMake and a C++17 compiler — for the zone engine library tests (only needed if you touch firmware).
- ESPHome CLI — for firmware compile, if you're building firmware locally.
pip install esphomeworks. lcov(optional) — used by the pre-push hook for C++ coverage on firmware changes.brew install lcovon macOS. Without it, the hook skips C++ coverage but still runs C++ tests.
A locally-running Home Assistant is helpful but not required for running the test suite — the Python tests use pytest-homeassistant-custom-component, which provides fixtures that don't need a real HA instance.
Clone and set up¶
git clone https://github.com/clintongormley/everything-presence-pro-grid
cd everything-presence-pro-grid
# Python environment
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements_test.txt
# Frontend dependencies
cd frontend
npm install
cd ..
For docs work, also install the docs dependencies:
Install the pre-push hook¶
The repository ships a pre-push hook at scripts/hooks/pre-push that runs format, lint, tests, and coverage across Python, TypeScript, and C++ before every push. It's not optional for this project — CI runs the same checks and they need to pass.
Install it:
Building the frontend bundle¶
The HA panel is TypeScript compiled to a single bundled JS file:
This produces custom_components/eppgrid/frontend/eppgrid-panel.js.
This bundle is committed to the repository. Home Assistant serves it as a static file — there's no build step at HA-install time. That means any frontend source change must be rebuilt and the resulting eppgrid-panel.js committed alongside the source changes. The pre-push hook runs npm run build so the bundle is always up to date in your commit.
Running tests¶
Each layer has its own test command. The pre-push hook runs all of them, but during development you usually only want the relevant one.
Python¶
- Covers the integration's Python code (device manager, storage, WebSocket API, flasher, diagnostics, config flow).
- Coverage floor: 90%. The hook fails the push if coverage drops below.
- Uses
pytest-homeassistant-custom-componentfor HA fixtures.
Frontend¶
- Covers components, controllers, and
lib/modules. - Per-file coverage thresholds (from
frontend/vitest.config.ts): lines 90%, branches 85%, functions 90%, statements 90%. Vitest fails the run if any file falls below these, which fails the push and CI.
C++ zone engine¶
cd firmware/lib/epp_zone_engine
cmake -B build -S . -DCMAKE_BUILD_TYPE=Coverage
cmake --build build
ctest --test-dir build --output-on-failure
- Only needed if you touch firmware code. The hook runs this automatically when firmware files change.
- Coverage floor (hook-enforced): 90%, measured with
lcov.
Docs¶
The docs site builds with MkDocs; --strict is required to catch broken links.
There are no unit tests for docs — --strict is the verification.
What the pre-push hook runs¶
In order, on every git push:
- Docs check — if model files change (Python integration code or frontend source under
lib/,controllers/,components/, or top-level), warns (non-blocking) ifdocs/developers/architecture.mdordocs/developers/data-catalog.mdwasn't also updated. - Strings check — if entity files changed with likely user-facing string edits, fails if
strings.jsonwasn't also updated. - Translations check —
strings.json↔translations/en.jsonmust be identical (copy one to the other if you change strings). - Python format —
ruff format --check. - Python lint —
ruff check. - Python tests + coverage —
pytestwith 90% floor. - C++ build / tests / coverage (only if firmware code changed) — CMake + CTest +
lcovwith 90% floor. - TypeScript format — Biome.
- TypeScript lint — Biome.
- TypeScript build — Rollup, which produces the committed bundle.
- TypeScript tests + coverage — Vitest.
If any step fails, the push aborts. Fix the issue, re-stage, and push again.
Tip
Running the hook locally via ./scripts/hooks/pre-push origin main is a faster way to catch failures than waiting for a rejected push.
PR process¶
- Work on a feature branch cut off
main. - Make small, focused commits — one concern per commit. See commit style below.
- When ready, open a PR against
main. - CI runs the same checks as the pre-push hook, plus the firmware-compile matrix and HACS / Hassfest / CodeQL.
- Respond to review comments inline. The repo uses regular merge commits (not squash) as its merge strategy — keep your history clean before requesting merge.
- Once approved and green, merge via the GitHub UI ("Create a merge commit").
Commit-message style¶
Observed convention from git log: type: summary where type is one of docs, feat, fix, chore, test, refactor. Keep the subject under ~70 characters; expand in the body when the "why" isn't obvious from the diff.
Examples from this repo's history:
feat: automate firmware publishing on releasefix: detect device going offline during OTA from device listdocs: write Firmware pagechore: drop committed fw/ — Pages stages from latest release
What not to commit¶
.gitignore already excludes the usual suspects (.venv/, __pycache__/, node_modules/, dist/, site/), plus planning artefacts under docs/superpowers/. If you've added a docs/superpowers/ spec or plan while designing your change, leave it local — it won't be committed or pushed.