Home Assistant Lazy Context
Use this skill when reviewing or editing Home Assistant frontend context, memoization, or hass removal work.
Default Migration Rule
When a component adopts context, remove its hass property and replace each this.hass.* access with the narrowest context or value that supplies that data. Also remove .hass=${...} passthroughs to migrated child components; if a child still needs data, either migrate the child to context or pass a narrow prop such as localize, states, or Pick<HomeAssistant, "callWS">.
Do not keep hass!: HomeAssistant just to preserve the old public API unless the component is intentionally reused outside the app context tree or the user explicitly asks for compatibility.
Context Architecture (src/data/context/index.ts)
Three tiers of Lit contexts provided by context-mixin.ts:
Core Contexts (always subscribed)
| Context |
Type |
Contents |
registriesContext |
HomeAssistantRegistries |
entities, devices, areas, floors registries |
statesContext |
HomeAssistant["states"] |
Live entity state map |
servicesContext |
HomeAssistant["services"] |
Services map |
internationalizationContext |
HomeAssistantInternationalization |
localize, locale, language, loaders |
apiContext |
HomeAssistantApi |
callService, callWS, fetchWithAuth, hassUrl |
connectionContext |
HomeAssistantConnection |
connection, connected, debugConnection |
uiContext |
HomeAssistantUI |
themes, panels, sidebar, kiosk |
configContext |
HomeAssistantConfig |
auth, config, user, userData, systemData |
formattersContext |
HomeAssistantFormatters |
formatEntityState, formatEntityName, etc. |
entitiesContext |
HomeAssistant["entities"] |
Entity registry map |
devicesContext |
HomeAssistant["devices"] |
Device registry map |
areasContext |
HomeAssistant["areas"] |
Area registry map |
floorsContext |
HomeAssistant["floors"] |
Floor registry map |
Lazy Contexts (subscribed only when consumed)
Managed by LazyContextProvider -- WS subscription defers until first consumer, tears down after 5s idle when all consumers disconnect.
| Context |
Type |
labelsContext |
LabelRegistryEntry[] |
fullEntitiesContext |
EntityRegistryEntry[] |
configEntriesContext |
ConfigEntry[] |
manifestsContext |
DomainManifestLookup |
Deprecated Contexts
Do not use. Each has a @deprecated comment naming the replacement:
connectionSingleContext → connectionContext
localizeContext, localeContext → internationalizationContext
configSingleContext, userContext, userDataContext, authContext → configContext
themesContext, selectedThemeContext, panelsContext → uiContext
Entity Decorators (src/common/decorators/consume-context-entry.ts)
@consumeEntityState({ entityIdPath }) — resolves entity ID from host config path, subscribes to statesContext, returns HassEntity
@consumeEntityStates({ entityIdPath }) — same for array of entity IDs → HassEntity[]
@consumeEntityRegistryEntry({ entityIdPath }) — subscribes to entitiesContext, returns EntityRegistryDisplayEntry
@consumeLocalize() — subscribes to internationalizationContext and narrows it to LocalizeFunc
Use @consumeLocalize() when a component only needs localize. Use internationalizationContext directly when it also needs locale, language, translationMetadata, loadBackendTranslation, or loadFragmentTranslation.
Migration: Removing hass
When a component adopts context, the hass property is removed. This requires revising all sub-components and helpers it passes hass to.
Decision Tree for Sub-Components
Component can consume context directly (it's rendered in the app tree, not reused across unrelated trees):
- Remove
hass property, add @consume decorators for each needed slice.
- Use
ContextType<typeof fooContext> for typing.
Component is a shared utility (reused widely, context conversion would touch too many callers):
- Pass the individual item as a prop when scope is small (e.g.
localize: LocalizeFunc, states: HassEntities).
- Pass
Pick<HomeAssistant, "callWS" | "localize"> when the component needs a few unrelated slices and context is not viable.
- Never pass the full
HomeAssistant object.
Helper/utility functions (pure data functions):
- Narrow the parameter from
HomeAssistant to Pick<HomeAssistant, "callWS"> (or the single value needed).
- This lets the consuming component pass its context slice directly (e.g.
this._api) without reconstructing hass.
- Prefer narrowing the helper over changing every caller — keeps the scope of the change small.
Narrowing Helpers to Avoid Scope Explosion
When a component adopts context, its helpers still need data. Rather than passing a reconstructed hass-like object or converting every caller, narrow the helper signature so the context slice satisfies it directly:
// BEFORE: helper accepts full hass
export const fetchDeviceTriggers = (hass: HomeAssistant, deviceId: string) =>
hass.callWS<DeviceTrigger[]>({...});
// AFTER: helper accepts only what it uses
export const fetchDeviceTriggers = (
hass: Pick<HomeAssistant, "callWS">,
deviceId: string
) => hass.callWS<DeviceTrigger[]>({...});
The component then passes its context slice directly:
// Component consumes apiContext (which satisfies Pick<HomeAssistant, "callWS">)
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: ContextType<typeof apiContext>;
// Passes directly — no reconstruction needed
const triggers = await fetchDeviceTriggers(this._api, deviceId);
This keeps the change focused: the dialog/panel adopts context, the helpers get narrowed types, and all other callers of those helpers still work unchanged (since HomeAssistant satisfies Pick<HomeAssistant, "callWS">).
When to Use Pick vs Individual Values
Pick<HomeAssistant, "callWS"> — when the helper uses methods from a single context group and the existing param name hass stays readable. Keeps changes minimal.
- Individual value (e.g.
states: HassEntities) — when passing a plain data map that the helper iterates/reads. More explicit, avoids the hass. prefix.
Pick with multiple keys — avoid unless strictly necessary. If a helper needs "callWS" | "localize", consider splitting it or accepting the grouped context type directly.
Consumption Pattern
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: ContextType<typeof apiContext>;
@state()
@consume({ context: internationalizationContext, subscribe: true })
private _i18n!: ContextType<typeof internationalizationContext>;
@state()
@consume({ context: statesContext, subscribe: true })
private _states!: ContextType<typeof statesContext>;
Usage: this._api.callWS(...), this._i18n.localize(...), this._states[entityId].
For localize-only components, prefer the helper:
@consumeLocalize()
private _localize!: LocalizeFunc;
Rules
General
- Preserve runtime behavior and public API shape except for the
hass API intentionally removed by context migrations.
- Use the narrowest correct data source; prefer contexts, lazy contexts, and entity decorators over broad
hass access.
- Do not use deprecated contexts; use the replacement named in
@deprecated comments.
- Do not replicate
hass or build local objects that mirror large parts of HomeAssistant.
- Do not add provider fallbacks when an existing app-level context/lazy context is already correct.
- Do not duplicate
LazyContextProvider lifecycle behavior in feature components.
- Do not create wrapper helpers that only forward existing utility calls.
Typing
- Use
ContextType<typeof context> for consumed context typing.
- Use required (
!) when lifecycle guarantees availability; use optional (?) only when value can be genuinely absent.
- Avoid excessive defensive guards around required consumed contexts.
- For sub-components that can't use context, type props as narrow as possible: prefer
LocalizeFunc over HomeAssistantInternationalization, prefer Pick<HomeAssistant, "callWS"> over HomeAssistant.
Memoization
- Pass explicit narrow inputs only (actual state slice, config, primitive, small arrays).
- Do not pass broad objects (
hass, this, large mutable maps) into memoizeOne.
- Do not widen signatures with pass-through
localize, language, locale, or config values when class/context access already exists.
- Do not add passthrough fields (
_language, _locale, _config) only to route values into helpers.
- For rarely changed values like locale/config, read from existing class/context access at point of use.
Style
- Do not add unnecessary comments or abstractions.
1---2name: home-assistant-lazy-context3description: Home Assistant frontend lazy-context, memoization, and `hass` removal guidance. Use when migrating Lit components from `hass!: HomeAssistant`, `.hass=${...}`, or broad `hass` access to context slices.4license: Apache-2.05---67# Home Assistant Lazy Context89Use this skill when reviewing or editing Home Assistant frontend context, memoization, or `hass` removal work.1011## Default Migration Rule1213When a component adopts context, remove its `hass` property and replace each `this.hass.*` access with the narrowest context or value that supplies that data. Also remove `.hass=${...}` passthroughs to migrated child components; if a child still needs data, either migrate the child to context or pass a narrow prop such as `localize`, `states`, or `Pick<HomeAssistant, "callWS">`.1415Do not keep `hass!: HomeAssistant` just to preserve the old public API unless the component is intentionally reused outside the app context tree or the user explicitly asks for compatibility.1617## Context Architecture (`src/data/context/index.ts`)1819Three tiers of Lit contexts provided by `context-mixin.ts`:2021### Core Contexts (always subscribed)2223| Context | Type | Contents |24|---------|------|----------|25| `registriesContext` | `HomeAssistantRegistries` | entities, devices, areas, floors registries |26| `statesContext` | `HomeAssistant["states"]` | Live entity state map |27| `servicesContext` | `HomeAssistant["services"]` | Services map |28| `internationalizationContext` | `HomeAssistantInternationalization` | localize, locale, language, loaders |29| `apiContext` | `HomeAssistantApi` | callService, callWS, fetchWithAuth, hassUrl |30| `connectionContext` | `HomeAssistantConnection` | connection, connected, debugConnection |31| `uiContext` | `HomeAssistantUI` | themes, panels, sidebar, kiosk |32| `configContext` | `HomeAssistantConfig` | auth, config, user, userData, systemData |33| `formattersContext` | `HomeAssistantFormatters` | formatEntityState, formatEntityName, etc. |34| `entitiesContext` | `HomeAssistant["entities"]` | Entity registry map |35| `devicesContext` | `HomeAssistant["devices"]` | Device registry map |36| `areasContext` | `HomeAssistant["areas"]` | Area registry map |37| `floorsContext` | `HomeAssistant["floors"]` | Floor registry map |3839### Lazy Contexts (subscribed only when consumed)4041Managed by `LazyContextProvider` -- WS subscription defers until first consumer, tears down after 5s idle when all consumers disconnect.4243| Context | Type |44|---------|------|45| `labelsContext` | `LabelRegistryEntry[]` |46| `fullEntitiesContext` | `EntityRegistryEntry[]` |47| `configEntriesContext` | `ConfigEntry[]` |48| `manifestsContext` | `DomainManifestLookup` |4950### Deprecated Contexts5152Do not use. Each has a `@deprecated` comment naming the replacement:5354- `connectionSingleContext` → `connectionContext`55- `localizeContext`, `localeContext` → `internationalizationContext`56- `configSingleContext`, `userContext`, `userDataContext`, `authContext` → `configContext`57- `themesContext`, `selectedThemeContext`, `panelsContext` → `uiContext`5859## Entity Decorators (`src/common/decorators/consume-context-entry.ts`)6061- `@consumeEntityState({ entityIdPath })` — resolves entity ID from host config path, subscribes to `statesContext`, returns `HassEntity`62- `@consumeEntityStates({ entityIdPath })` — same for array of entity IDs → `HassEntity[]`63- `@consumeEntityRegistryEntry({ entityIdPath })` — subscribes to `entitiesContext`, returns `EntityRegistryDisplayEntry`64- `@consumeLocalize()` — subscribes to `internationalizationContext` and narrows it to `LocalizeFunc`6566Use `@consumeLocalize()` when a component only needs `localize`. Use `internationalizationContext` directly when it also needs `locale`, `language`, `translationMetadata`, `loadBackendTranslation`, or `loadFragmentTranslation`.6768## Migration: Removing `hass`6970When a component adopts context, the `hass` property is removed. This requires revising all sub-components and helpers it passes `hass` to.7172### Decision Tree for Sub-Components73741. **Component can consume context directly** (it's rendered in the app tree, not reused across unrelated trees):75 - Remove `hass` property, add `@consume` decorators for each needed slice.76 - Use `ContextType<typeof fooContext>` for typing.77782. **Component is a shared utility** (reused widely, context conversion would touch too many callers):79 - Pass the **individual item** as a prop when scope is small (e.g. `localize: LocalizeFunc`, `states: HassEntities`).80 - Pass `Pick<HomeAssistant, "callWS" | "localize">` when the component needs a few unrelated slices and context is not viable.81 - Never pass the full `HomeAssistant` object.82833. **Helper/utility functions** (pure data functions):84 - Narrow the parameter from `HomeAssistant` to `Pick<HomeAssistant, "callWS">` (or the single value needed).85 - This lets the consuming component pass its context slice directly (e.g. `this._api`) without reconstructing `hass`.86 - Prefer narrowing the helper over changing every caller — keeps the scope of the change small.8788### Narrowing Helpers to Avoid Scope Explosion8990When a component adopts context, its helpers still need data. Rather than passing a reconstructed `hass`-like object or converting every caller, **narrow the helper signature** so the context slice satisfies it directly:9192```ts93// BEFORE: helper accepts full hass94export const fetchDeviceTriggers = (hass: HomeAssistant, deviceId: string) =>95 hass.callWS<DeviceTrigger[]>({...});9697// AFTER: helper accepts only what it uses98export const fetchDeviceTriggers = (99 hass: Pick<HomeAssistant, "callWS">,100 deviceId: string101) => hass.callWS<DeviceTrigger[]>({...});102```103104The component then passes its context slice directly:105106```ts107// Component consumes apiContext (which satisfies Pick<HomeAssistant, "callWS">)108@state()109@consume({ context: apiContext, subscribe: true })110private _api!: ContextType<typeof apiContext>;111112// Passes directly — no reconstruction needed113const triggers = await fetchDeviceTriggers(this._api, deviceId);114```115116This keeps the change focused: the dialog/panel adopts context, the helpers get narrowed types, and all other callers of those helpers still work unchanged (since `HomeAssistant` satisfies `Pick<HomeAssistant, "callWS">`).117118### When to Use `Pick` vs Individual Values119120- **`Pick<HomeAssistant, "callWS">`** — when the helper uses methods from a single context group and the existing param name `hass` stays readable. Keeps changes minimal.121- **Individual value** (e.g. `states: HassEntities`) — when passing a plain data map that the helper iterates/reads. More explicit, avoids the `hass.` prefix.122- **`Pick` with multiple keys** — avoid unless strictly necessary. If a helper needs `"callWS" | "localize"`, consider splitting it or accepting the grouped context type directly.123124### Consumption Pattern125126```ts127@state()128@consume({ context: apiContext, subscribe: true })129private _api!: ContextType<typeof apiContext>;130131@state()132@consume({ context: internationalizationContext, subscribe: true })133private _i18n!: ContextType<typeof internationalizationContext>;134135@state()136@consume({ context: statesContext, subscribe: true })137private _states!: ContextType<typeof statesContext>;138```139140Usage: `this._api.callWS(...)`, `this._i18n.localize(...)`, `this._states[entityId]`.141142For localize-only components, prefer the helper:143144```ts145@consumeLocalize()146private _localize!: LocalizeFunc;147```148149## Rules150151### General152153- Preserve runtime behavior and public API shape except for the `hass` API intentionally removed by context migrations.154- Use the narrowest correct data source; prefer contexts, lazy contexts, and entity decorators over broad `hass` access.155- Do not use deprecated contexts; use the replacement named in `@deprecated` comments.156- Do not replicate `hass` or build local objects that mirror large parts of `HomeAssistant`.157- Do not add provider fallbacks when an existing app-level context/lazy context is already correct.158- Do not duplicate `LazyContextProvider` lifecycle behavior in feature components.159- Do not create wrapper helpers that only forward existing utility calls.160161### Typing162163- Use `ContextType<typeof context>` for consumed context typing.164- Use required (`!`) when lifecycle guarantees availability; use optional (`?`) only when value can be genuinely absent.165- Avoid excessive defensive guards around required consumed contexts.166- For sub-components that can't use context, type props as narrow as possible: prefer `LocalizeFunc` over `HomeAssistantInternationalization`, prefer `Pick<HomeAssistant, "callWS">` over `HomeAssistant`.167168### Memoization169170- Pass explicit narrow inputs only (actual state slice, config, primitive, small arrays).171- Do not pass broad objects (`hass`, `this`, large mutable maps) into `memoizeOne`.172- Do not widen signatures with pass-through `localize`, `language`, `locale`, or config values when class/context access already exists.173- Do not add passthrough fields (`_language`, `_locale`, `_config`) only to route values into helpers.174- For rarely changed values like locale/config, read from existing class/context access at point of use.175176### Style177178- Do not add unnecessary comments or abstractions.