# Contributing to House Plan Thanks for your interest! The project is one HACS package: a storage **integration** (`custom_components/houseplan/`, Python) and a **Lovelace card** (`src/`, TypeScript + Lit). ## Changelog User-visible changes go into **both** changelogs in the same commit: `docs/CHANGELOG.md` (English) and `docs/CHANGELOG.ru.md` (Russian). Entries older than v1.42.0 exist only in the English file — no need to backfill them. ## Translations A shipped UI language has three matching parts: 1. `src/i18n/.json` for the card; 2. `custom_components/houseplan/translations/.json` for the Home Assistant integration; 3. one registry entry (code, native label and eager dictionary or lazy loader) in `src/i18n/registry.ts`. Use the canonical Home Assistant/BCP 47 language tag as `` (for example, `fr` or `pt-BR`) and use that exact spelling for both JSON filenames. Lookup is case-insensitive and also accepts `_` from legacy locale sources. The registry drives language resolution, the visual-editor selector and parity tests. The tests reject missing or extra locale files; frontend dictionaries also fail on mismatched keys, empty values and changed placeholders. Placeholders such as `{name}` and `{n}` are a contract: do not translate, add or remove them. English and Russian are synchronous fallback/legacy locales. German is the reference lazy third-locale implementation: its module carries the same build fingerprint as the entry bundle, has one content-hashed retry URL and is listed under `lazyLocaleFiles` in `houseplan-assets.json`. New sizeable locales should follow that path unless a measured initial-bundle budget explicitly justifies an eager import. Extend the runtime, manifest, file/key/placeholder parity and regional-locale tests together; never bypass the registry by importing a locale directly in a component. Administrator-only copy lives in three lazy namespace dictionaries — `src/i18n/settings/`, `src/i18n/support/` and `src/i18n/topology/` (#627). Their English file is static (the synchronous fallback); every other language is one lazy chunk per namespace × language: a two-line loader module `src/i18n//-.ts`, one `import()` with a content-hashed retry token in `src/i18n/.ts`, and one entry in `NAMESPACE_LOCALE_CHUNKS` in `scripts/bundle-manifest.mjs`. The lazy surfaces (onboarding, editor runtime, Zigbee overlay) wait for their dictionaries before painting; never import a non-English namespace JSON statically — the budget gate and `test/i18n-lazy-namespaces.test.mjs` refuse it. The current `subst()` helper does not implement plural rules. Phrase strings so their grammar does not depend on the numeric value (for example, use a neutral label followed by `{n}` rather than an English singular/plural pair). Adding a UI locale does not automatically create another full documentation set; maintain the existing English and Russian documentation according to the project's normal rules. Right-to-left layout is a separate product project, because the plan canvas and editors cannot be mirrored by translations alone. ## Where to ask Not sure whether something is a bug, or just want to discuss an idea before writing code? The **[Telegram chat @ha_houseplan](https://t.me/ha_houseplan)** is the quickest route to the author and other users. Bugs and concrete feature requests still belong in [issues](https://github.com/Matysh/houseplan-card/issues). ## Backlog and work status [GitHub Issues](https://github.com/Matysh/houseplan-card/issues) are the only active backlog. An issue owns scope and acceptance criteria; its **labels** own priority and workflow status — `PROCESS.md` §9 holds the vocabulary. Before starting planned work, link it to an existing issue or create one, and keep it current until the verified result is closed. Design specs and ADRs may support an issue, but they do not replace it or maintain a separate checklist. ## Five-minute setup ```bash git clone --filter=blob:none https://github.com/Matysh/houseplan-card && cd houseplan-card npm ci # frontend toolchain npm run typecheck # tsc --noEmit (strict) npm test # node:test — pure logic, i18n parity, tap-action security npm run build # tsc + rollup → dist/houseplan-card.js pip install pytest voluptuous && python -m pytest tests_backend -q # pure backend tests npm install # also installs .githooks through the prepare script ``` ### Why `--filter=blob:none` (#345) A full clone is **215 MB of `.git`**; a blobless one is **26 MB** — measured, not estimated. Both carry all 1611 commits and all 182 tags, so ranges, `merge-base` and `git diff` across history work identically; `git diff origin/dev~3..origin/dev` in a blobless clone takes about a second and grows `.git` by one megabyte. The difference is that historical *file contents* are fetched only if something actually asks for them. That matters here because 32% of the pack is documentation screenshots — ten PNGs re-captured 196 times — and another sizeable share is the committed bundle, one 1.16 MB file per product change. Almost nobody ever reads an old revision of either. Drop the flag if you work offline with history, or need `git log -p` over the whole tree repeatedly. Do **not** replace it with `--depth=1`: a shallow clone is about the same size but has no `merge-base`, so the process gate, `smoke-select` and every `origin/dev..HEAD` range stop working. The HA-harness backend tests (`tests_backend/test_ha_*.py`) need the repository-pinned Python (`.python-version`) and `pytest-homeassistant-custom-component home-assistant-frontend`; their canon is Linux CI or WSL (`bash scripts/wsl-setup.sh --verify`). Without an importable `homeassistant` they are not collected at all — not skipped — and pytest prints `HA harness NOT collected: …` (#630). ## Ground rules - **Docs in the same commit**: CHANGELOG entry for user-visible changes; `docs/STATUS.md` for state changes; `docs/DEVELOPMENT.md` for new gotchas. - Every UI string goes through `src/i18n/.json`; follow the [Translations](#translations) flow for registry and backend parity. - The committed bundle changes only in a release candidate: `npm run bundle:release` in a commit with a `Release:` trailer (#657). An ordinary task restores it with `npm run bundle:clean` before committing — the `commit-msg` hook refuses a bundle change otherwise. - Tap actions have a security model (locks/alarms never toggle from the plan) — see `resolveToggleIntent` in `src/device-toggle.ts`; don't weaken it. - Every commit follows the issue and trailer contract in `PROCESS.md`. - Follow the Integration Quality Scale where applicable — `custom_components/houseplan/quality_scale.yaml` tracks the self-assessment. ## Architecture Start with `docs/ARCHITECTURE.md` (data model, WS API, coordinate system) and `docs/STATUS.md` (current state). Release mechanics live in `docs/STATUS.md` › Workflow: the version sources are the ones checked by `scripts/release-contract.mjs`, prereleases go through `npm run release:prerelease -- --issues=… --yes` (or the manual `Publish prerelease` workflow), and stable installable assets are published only by `release.yml` (#540).