This document covers local development setup, the frontend source layout, internationalisation, remote HA development, the WebSocket API, and CI/release notes.
git clone https://github.com/buggedcom/HASS-Data-Points.git
cd HASS-Data-Points
corepack enable
pnpm install
pnpm hooks:installpnpm buildThe built frontend bundle is committed to the repo as:
custom_components/hass_datapoints/hass-datapoints-cards.js
pnpm test
pnpm vitest run <focused spec files>The published Storybook for the main branch is available at:
https://main--69cd024f27ae313c14343a9a.chromatic.com
To run Storybook locally or build it:
pnpm sb
pnpm sb:buildThe frontend lives in:
custom_components/hass_datapoints/src/
Current top-level structure:
src/
├── atoms/
├── cards/
├── charts/
├── components/
├── lib/
├── molecules/
├── panels/
└── test-support/
| Directory | Contents |
|---|---|
atoms/ |
Reusable UI primitives |
molecules/ |
Composed reusable UI units |
cards/ |
Feature cards — action, quick, list, dev tool, history, statistics, sensor |
charts/ |
Shared chart infrastructure — base classes, DOM helpers, interaction utilities |
panels/datapoints/ |
The dedicated Datapoints history page |
lib/ |
Shared chart logic, HA helpers, domain logic, workers, i18n, and utilities |
The frontend uses @lit/localize in runtime mode.
Source locale: English (en) — all user-visible strings in the source code are written in English.
Supported translated locales: German (de), Spanish (es), Finnish (fi), French (fr), Portuguese (pt), Simplified Chinese (zh-Hans).
The canonical list of supported locales is maintained in a single file:
src/lib/i18n/supported-locales.json
Both localize.ts and the translation coverage tests read from this file, so adding a locale there is the only registration step needed.
src/lib/i18n/localize.ts calls configureLocalization once at startup. The user's Home Assistant UI language is read from hass.locale.language (falling back to hass.language) and normalised to the nearest supported locale — for example fr-CA resolves to fr. The matching locale chunk is then lazy-loaded and components decorated with @localized() re-render automatically.
Every user-visible string is wrapped with msg():
import { msg, localized } from "@/lib/i18n/localize";
@localized()
class MyElement extends LitElement {
render() {
return html`<span>${msg("Save page state")}</span>`;
}
}Interpolated strings that contain runtime values cannot be passed directly to msg(). Use numbered placeholders and a t() helper instead:
function t(key: string, ...values: string[]): string {
let s = msg(key, { id: key });
values.forEach((v, i) => {
s = s.replace(new RegExp(`\\{${i}\\}`, "g"), v);
});
return s;
}
// Usage
t("Anomaly at {0} with value {1}", formattedTime, formattedValue);Translations are not in a single central locale file. Each component that has translatable strings owns an i18n/ subdirectory containing one file per locale:
src/molecules/target-row/
├── target-row.ts
└── i18n/
├── de.ts
├── es.ts
├── fi.ts
├── fr.ts
├── pt.ts
└── zh-hans.ts
Every locale file exports a translations object typed as ComponentTranslations:
import type { ComponentTranslations } from "@/lib/i18n/types";
export const translations: ComponentTranslations = {
"Show anomalies": "Näytä anomaliat",
Sensitivity: "Herkkyys",
};Each src/lib/i18n/locales/<locale>.ts file uses import.meta.glob to discover and merge every matching i18n/<locale>.ts file across the entire source tree:
// src/lib/i18n/locales/fi.ts
const modules = import.meta.glob<{ translations: Record<string, string> }>(
"../../../**/i18n/fi.ts",
{ eager: true }
);
const merged: Record<string, string> = {};
for (const mod of Object.values(modules)) {
Object.assign(merged, mod.translations);
}
export const templates = merged satisfies LocaleModule["templates"];No manual registration is needed. Creating an i18n/fi.ts file anywhere under src/ is sufficient for its strings to be included in the built locale chunk.
Duplicate keys are resolved by last-writer-wins (Object.assign). This is safe because any shared key (e.g. "Auto") carries the same translated value regardless of which component declares it.
src/lib/i18n/__tests__/translations-coverage.spec.ts enforces three rules across every component i18n/ directory automatically:
- Locale presence — every supported locale file must exist.
- Key completeness — every locale file must contain exactly the same set of keys (no missing translations, no stale extras left over after a key is renamed or removed).
- Value completeness — every translated value must differ from its English source key, unless the string is listed in
UNTRANSLATED_WHITELIST(reserved for technical terms, proper nouns, and abbreviations that are genuinely the same across languages).
Run the tests with pnpm test to catch any gaps before committing.
- Create an
i18n/subdirectory next to the component source file. - Add a
<locale>.tsfile for each locale listed insupported-locales.json. - Each file exports a
translationsobject with the same keys (English source string → translated value). - Wrap every user-visible string in the component with
msg()and add@localized()to the class. - Run
pnpm test— the coverage tests will fail immediately if any locale file is missing or has mismatched keys.
- Add the locale code to
src/lib/i18n/supported-locales.json. - Create
src/lib/i18n/locales/<locale>.tswith theimport.meta.globpattern above, substituting the new locale code. - Add a
casefor the new locale in theloadLocaleswitch inlocalize.ts. - Add a normalisation branch in
normalizeLocaleinlocalize.tsto map BCP 47 variants (e.g.pt-BR) to the canonical code. - Add an
i18n/<locale>.tsfile to every component directory that already has ani18n/subdirectory — the coverage tests will list exactly which ones are missing.
The English translations were written by a native speaker. Finnish translations were written by a non-native speaker. All other bundled locales are currently machine-translated — they are included so the UI is usable out of the box in more Home Assistant setups, but they should be treated as reasonable defaults rather than fully reviewed translations.
Translation improvements for any locale are welcome — edit the relevant i18n/<locale>.ts and json files and open a pull request.
If you use a remote HA instance for development:
- Copy the example env file:
cp .env.dev.example .env.dev- Fill in the remote host details.
- Sync manually:
pnpm dev:sync- Or run watch mode:
pnpm dev:watchThe frontend uses the following WebSocket commands:
| Type | Purpose |
|---|---|
hass_datapoints/events |
Fetch recorded datapoints/events |
hass_datapoints/events_bounds |
Fetch earliest/latest datapoint time |
hass_datapoints/events/update |
Update an existing datapoint (admin) |
hass_datapoints/events/delete |
Delete a datapoint (admin) |
hass_datapoints/events/delete_dev |
Delete dev datapoints (admin) |
hass_datapoints/history |
Fetch history/downsampled chart data |
hass_datapoints/anomalies |
Fetch backend anomaly results |
hass_datapoints/cache/clear |
Clear anomaly cache entries |
hass_datapoints/monitors/list |
List anomaly monitors |
hass_datapoints/monitors/create |
Create anomaly monitor |
hass_datapoints/monitors/update |
Update anomaly monitor |
hass_datapoints/monitors/delete |
Delete anomaly monitor |
hass_datapoints/monitors/anomalies |
Fetch live monitor anomalies |
hass_datapoints/monitors/dismiss |
Dismiss anomaly window |
hass_datapoints/monitors/undismiss |
Remove dismissal window |
Events are stored in:
.storage/hass_datapoints.events
Per-monitor Home Assistant sensor entities also expose compact anomaly summaries through state attributes for automations and MCP consumers. These summaries are restart-safe and intentionally bounded, while the richer websocket cluster payloads remain the source of truth for frontend detail views.
- CI checks build correctness and integration metadata.
- The built frontend bundle is committed as
custom_components/hass_datapoints/hass-datapoints-cards.js. - Pre-commit hooks format staged files, lint package.json versions, validate frontend types, and rebuild the frontend when needed.
- Pre-push hooks run tests, lint checks, package.json version linting, and frontend type validation before pushing.