Commit Graph
48 Commits
Author SHA1 Message Date
Dermot Duffy ea251ca988 perf(bundle): lazy-load the nunjucks template engine (#2535)
Closes #2531.

## Summary

The full `nunjucks` templating engine (~226KB) plus `ha-nunjucks`
(~46KB) — roughly **272KB, ~13% of the eager entry chunk** — was
statically imported and downloaded by every card on initial load, even
though templates only apply when a config value contains a `{{ … }}` /
`{% … %}` delimiter. Most cards use no templates and never need the
engine.

This defers the engine behind a dynamic `import('ha-nunjucks/dist')` so
it ships in a separate, on-demand chunk instead of the eager
`card-*.js`.

## Approach

The render path (`TemplateRenderer.renderRecursively`) is **kept
synchronous** — it is called from many synchronous hot paths
(condition/trigger evaluators, picture-elements rendering, actions,
folder matchers), and making it async would be a large, high-risk
refactor of the evaluation core.

Instead:

- **New `src/card-controller/templates/engine.ts`** — a module-level
singleton lazy loader (`loadTemplateEngine()` / `getTemplateEngine()`)
shared across all `TemplateRenderer` instances, plus a
`containsTemplate()` delimiter helper.
- **Delimiter gating** — strings without a delimiter never touch the
engine (the overwhelming majority of renders).
- **Pre-warm at config time** — because every template string originates
in the config, a new mandatory `TEMPLATE_ENGINE` initialization aspect
loads the engine before first render whenever the config contains a
delimiter. This **guarantees no raw `{{ … }}` flash**: content/condition
rendering is blocked until the engine is present for template-using
cards. Cards without templates never load it.

## Result

- nunjucks + ha-nunjucks move out of the eager chunk into a separate
chunk fetched only when a card actually uses templates.
- No change to the synchronous public render API; condition/trigger
evaluation core untouched.

## Tests

- New `engine.ts` loader coverage (concurrent load, cached reuse,
not-loaded fallback).
- The 11 existing test files that render real (delimiter-bearing)
templates declare their dependency explicitly via
`beforeAll(loadTemplateEngine)` — no global/implicit setup hook.
- Full suite green (4764 tests), lint and ts-prune clean, per-file 100%
coverage maintained for the affected directories.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_016ykWemdkZrgywvC71pHc6c

---
_Generated by [Claude
Code](https://claude.ai/code/session_016ykWemdkZrgywvC71pHc6c)_
2026-06-30 17:45:13 -07:00
Dermot Duffy 7dc29865b8 chore: Enforce consistent type imports via @typescript-eslint/consistent-type-imports (#2545) 2026-06-30 17:45:13 -07:00
Dermot Duffy a96dc2e863 chore: Enforce import order via @ianvs/prettier-plugin-sort-imports (#2542) 2026-06-30 17:45:13 -07:00
Dermot Duffy 95f35bd326 refactor: Accept unknown in errorToConsole to drop as Error casts (#2541) 2026-06-30 17:45:13 -07:00
Dermot Duffy 5572ec728e refactor: Centralize template render type assertion (#2538) 2026-06-30 17:45:13 -07:00
Dermot DuffyandClaude Opus 4.8 b701366762 feat: Align automations with Home Assistant triggers and conditions (#2527)
Split automations into HA-style `triggers`, ongoing `conditions`, and
`actions`, with compatibility migrations for existing Advanced Camera
Card configs.

## Summary

At a glance (details below):

- **Added** `triggers:` -- a required, HA-shaped block: stock `state` /
`numeric_state` / `template` plus card-specific triggers (`camera`,
`view`, `fullscreen`, ...).
- **Added** the HA-native `if` / `then` / `else` action.
- **Removed** `actions_not` (replaced by `if` / `then` / `else`).
- **Removed** the ambient `advanced_camera_card` template namespace (use
`acc` instead).
- **Changed** the trigger template surface to a top-level `trigger.*`
variable (as in HA); the nested `acc.trigger.*` paths are removed.
- **Changed** `conditions:` to ongoing gates only -- they no longer wake
an automation, and change-only forms (`config`, valueless `camera` /
`view` / `state`) become triggers, not conditions.
- **Changed** action templates to render per step, so a later action
sees state an earlier one changed.
- **Compatibility:** HA-shaped YAML is accepted (singular keys,
single-or-list, `and` / `or` / `not` shorthand, `entity` / `entity_id`).
- **Migration:** existing configs upgrade automatically; anything that
cannot be converted faithfully is recorded under `__UPGRADE_FAILURE__`
for manual fixup.

## Breaking Changes

### 1. Automations now require triggers

Before this PR, `automations[].conditions` served two roles:

- They decided whether the automation should run.
- They also acted as the thing that woke the automation up.

After this PR:

- `triggers` wake the automation.
- `conditions` only gate it at the instant a trigger fires.

Most existing automations are migrated automatically from `conditions:`
to `triggers:`.

### 2. `actions_not` is retired

Legacy `actions_not` is replaced by an HA-style `if` action with `then`
/ `else`.

Faithful conversions are automatic. Cases that cannot be faithfully
converted are recorded under `__UPGRADE_FAILURE__.automations` and must
be migrated manually.

### 3. Template surface aligned with Home Assistant

Two related template changes, both auto-migrated:

- **Top-level `trigger.*`.** Automation actions now receive a top-level
`trigger` template variable, like Home Assistant. Legacy nested paths
such as `acc.trigger.state.to` and
`advanced_camera_card.trigger.camera.to` are migrated automatically when
they appear inside template strings.
- **The ambient `advanced_camera_card` template namespace is removed.**
The long-form ambient namespace (`advanced_camera_card.camera`,
`advanced_camera_card.view`, `advanced_camera_card.config`) is retired
in favour of its shorter `acc` alias -- supported since v7.1.0, and the
only spelling the new trigger surface uses. Existing templates are
migrated automatically by rewriting the `advanced_camera_card.` prefix
to `acc.`.

### 4. Trigger-only condition forms are no longer valid conditions

Some legacy "conditions" were really change detectors. These are now
triggers only:

- `condition: config`
- valueless `camera`
- valueless `view`
- valueless `state` / picture-elements state condition with neither
`state` nor `state_not`

These are automatically promoted in automations and stripped from
overrides/elements where they would no longer be meaningful as ongoing
conditions.

### 5. Template truthiness now follows Home Assistant behavior

Template conditions and template triggers intentionally use different
truthiness rules, matching HA:

- A template condition passes only when the rendered value is `true`
(case-insensitive), matching HA's `condition.py`.
- A template trigger uses HA's broader `result_as_boolean` coercion: a
non-zero number, or `1` / `true` / `yes` / `on` / `enable`
(case-insensitive), counts as true.

### 6. Action templates render when each action executes

Action templates are now rendered per action step, not once for the
whole sequence. This means a later action can see card-local state
changed by an earlier action in the same sequence.

The `trigger` context is fixed for the automation run. HA entity state
updates still depend on the frontend receiving updated HASS state over
the websocket.

## Automatic Migrations

### Automation `conditions:` to `triggers:`

Simple legacy automation:

```yaml
# Before
automations:
  - conditions:
      - condition: fullscreen
        fullscreen: true
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: substream_on
```

```yaml
# After, automatic
automations:
  - triggers:
      - trigger: fullscreen
        fullscreen: true
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: substream_on
```

State conditions become HA-style state triggers:

```yaml
# Before
automations:
  - conditions:
      - condition: state
        entity_id: binary_sensor.front_door
        state: 'on'
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: live
```

```yaml
# After, automatic
automations:
  - triggers:
      - trigger: state
        entity_id: binary_sensor.front_door
        to: 'on'
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: live
```

Multiple conditions become both triggers and ongoing conditions:

```yaml
# Before
automations:
  - conditions:
      - condition: camera
        cameras: [front_door]
      - condition: fullscreen
        fullscreen: true
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: substream_on
```

```yaml
# After, automatic
automations:
  - triggers:
      - trigger: camera
        cameras: [front_door]
      - trigger: fullscreen
        fullscreen: true
    conditions:
      - condition: camera
        cameras: [front_door]
      - condition: fullscreen
        fullscreen: true
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: substream_on
```

The flattened trigger list is an implicit OR. The retained `conditions:`
list is an implicit AND checked when any trigger fires.

### Trigger-only legacy conditions

Legacy `config` conditions become `config` triggers:

```yaml
# Before
automations:
  - conditions:
      - condition: config
        paths: [menu.style]
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: status_bar
```

```yaml
# After, automatic
automations:
  - triggers:
      - trigger: config
        paths: [menu.style]
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: status_bar
```

Trigger-only leaves are removed from retained `conditions:` blocks
because they no longer describe an ongoing state.

### `actions_not` to `if` / `then` / `else`

```yaml
# Before
automations:
  - conditions:
      - condition: state
        entity_id: input_boolean.camera_alerts
        state: 'on'
    actions:
      - action: custom:advanced-camera-card-action
        advanced_camera_card_action: live
    actions_not:
      - action: none
```

```yaml
# After, automatic
automations:
  - triggers:
      - trigger: state
        entity_id: input_boolean.camera_alerts
    actions:
      - if:
          - condition: state
            entity_id: input_boolean.camera_alerts
            state: 'on'
        then:
          - action: custom:advanced-camera-card-action
            advanced_camera_card_action: live
        else:
          - action: none
```

If the legacy automation had no conditions, or only trigger-only
conditions, `actions_not` is dropped because the old `else` branch could
not be reproduced as an ongoing predicate.

### Trigger template paths

```yaml
# Before
message: 'Door is {{ acc.trigger.state.to }} from {{ acc.trigger.state.from }}'
```

```yaml
# After, automatic
message: 'Door is {{ trigger.to_state.state }} from {{ trigger.from_state.state }}'
```

Path rewrites performed automatically:

| Old path                   | New path                   |
| -------------------------- | -------------------------- |
| `acc.trigger.state.entity` | `trigger.entity_id`        |
| `acc.trigger.state.from`   | `trigger.from_state.state` |
| `acc.trigger.state.to`     | `trigger.to_state.state`   |
| `acc.trigger.camera.from`  | `trigger.from_acc.camera`  |
| `acc.trigger.camera.to`    | `trigger.to_acc.camera`    |
| `acc.trigger.view.from`    | `trigger.from_acc.view`    |
| `acc.trigger.view.to`      | `trigger.to_acc.view`      |
| `acc.trigger.config.from`  | `trigger.from_acc.config`  |
| `acc.trigger.config.to`    | `trigger.to_acc.config`    |

The same rewrites are applied for the older
`advanced_camera_card.trigger.*` namespace.

### Ambient template namespace

Any remaining long-form ambient `advanced_camera_card.*` references
(outside the trigger surface) are rewritten to the `acc.*` alias:

```yaml
# Before
title: 'Now viewing {{ advanced_camera_card.camera }}'
```

```yaml
# After, automatic
title: 'Now viewing {{ acc.camera }}'
```

## Manual Migration Cases

### `__UPGRADE_FAILURE__.automations`

If a legacy automation cannot be converted faithfully, the original
automation is recorded under:

```yaml
__UPGRADE_FAILURE__:
  automations:
    - ...
```

These entries require manual migration.

The main known case is legacy `actions_not` with a condition whose
trigger can only fire on a rising edge, such as:

- `condition: template`
- `condition: screen`
- `condition: numeric_state` without an entity-backed state to watch

Those conditions can start the `then` branch, but cannot reliably start
the `else` branch when they stop matching.

### Unsupported HA conditions and triggers

This PR aligns the card with HA where supported, but it is not a full HA
automation engine.

Unsupported HA condition families include:

- `time`
- `zone`
- `sun`
- `location`
- `device`
- `condition: trigger`

Unsupported HA trigger platforms include:

- `event`
- `time`
- `time_pattern`
- `sun`
- `zone`
- `calendar`
- `webhook`
- `tag`
- `device`
- `mqtt`

The card-specific camera `triggers:` feature (which auto-selects and
wakes the card on camera events such as motion) is a separate feature
from automation `triggers:`, despite the shared word.

### Trigger IDs and variables

HA keys such as `id`, `alias`, and `variables` are accepted so pasted HA
YAML validates, but they are ignored by the card. There is no
`trigger.id` support in this PR.

## New Compatibility Features

This PR also makes card config more forgiving for HA-style YAML:

- `trigger`, `condition`, and `action` singular keys are accepted and
normalized to `triggers`, `conditions`, and `actions`.
- Single trigger, condition, and action objects are accepted where lists
are expected.
- `if`, `then`, and `else` accept a single item or a list.
- Composite condition shorthand is accepted:
  - `{ and: [...] }`
  - `{ or: [...] }`
  - `{ not: [...] }`
  - `{ condition: [...] }` as an implicit AND
- State conditions resolve expected state values that name another
entity, matching HA/Lovelace behavior.
- Both `entity` and `entity_id` are accepted on state and numeric
conditions and triggers (a superset of HA's two dialects), so there is
no forced rename.
- `state_not` remains supported as a card/Lovelace-friendly extension.

## Trigger Payloads

Automation action templates receive a top-level `trigger` object.

For stock `state` and `numeric_state` triggers:

```yaml
trigger.platform
trigger.entity_id
trigger.entity
trigger.from_state
trigger.to_state
```

For template triggers:

```yaml
trigger.platform
```

For card-specific triggers:

```yaml
trigger.platform # "acc"
trigger.type
trigger.from_acc
trigger.to_acc
```

The card does not currently expose HA's `id`, `idx`, `for`, `attribute`,
`above`, `below`, or `alias` trigger fields.

BREAKING CHANGE: Automations now follow Home Assistant's `triggers:` /
`conditions:` / `actions:` model. Automations require a `triggers:`
block and `conditions:` no longer wake an automation; `actions_not` is
removed in favour of an `if` / `then` / `else` action; the nested
`acc.trigger.*` template paths and the ambient `advanced_camera_card`
template namespace are removed (use the top-level `trigger.*` surface
and the `acc` alias); trigger-only condition forms (`config`, valueless
`camera` / `view` / `state`) are no longer valid conditions; and
template-condition vs template-trigger truthiness now follow HA.
Existing configs are upgraded automatically where a faithful conversion
exists; anything that cannot be converted is recorded under
`__UPGRADE_FAILURE__` for manual migration.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 17:45:13 -07:00
Dermot Duffy 47bcce93d3 feat: Add hardened error handling and retries (#2451)
- Closes #1830
 - Closes #2099
2026-06-30 17:45:12 -07:00
Dermot Duffy 0a67df9a25 refactor: Convert protected methods to private (#2358) 2026-02-19 20:47:59 -08:00
Dermot Duffy 7ce59be0fc fix: Remove status bar items correctly (#2268)
- Closes #2213
2025-12-08 22:31:46 -08:00
Dermot Duffy 9f479f8b4f fix: Expand templates in stock elements (#2199)
- Related: #2193
2025-10-06 21:14:29 -07:00
Dermot Duffy 54ee1e1cf3 fix: Improve reliability of connection cleanup (#2009)
- Closes #1992
2025-04-14 16:58:17 +01:00
Dermot Duffy d6225fefda refactor: Break apart monolithic configuration schema (#1977)
[skip ci]
2025-03-22 15:06:11 -07:00
Dermot Duffy a79ffa6edc refactor: Retire the use of unmaintained custom-card-helpers (#1956)
- Closes #1295
2025-03-09 16:02:33 -07:00
Dermot Duffy 8d3cf07b43 refactor: Significantly refactor how conditions work internally (#1886)
- Much improved API cleanliness and testability to allow further
extensibility in future
 - Simplified code in `live` view

This technically contains a small change in how overrides work in the
`live` view. Since that change is _closer_ to the documentation, and
since this is likely to be rarely used, this is not considered a
breaking change. Previously, overrides for a given live camera would
always render _as if_ that camera was selected, vs was actually
selected. Now, overrides will only apply in the live view when the
camera is _actually_ selected. If this is an issue for you in practice,
lets discuss.
2025-02-11 20:07:55 -08:00
Dermot Duffy cc376a8be1 feat: Rename the card to advanced-camera-card (#1873)
Whilst the Frigate support in this card is the best among camera
engines, the name incorrectly suggests that Frigate is a requirement.
Instead, to broaden the appeal, change to more camera agnostic name.
This does not suggest any change in priority, role or support for
Frigate.

This change is likely to be bug prone, due to the size of the rename --
the code contains 1500+ references to "Frigate" most of which make sense
to rename, some which do not, all of which needed human assessment.

- Closes #1298


BREAKING CHANGE: References to `frigate-card` in all kinds of
configuration need to be updated to `advanced-camera-card`. An automated
config upgrade should take care of the majority of usecases (click `Edit
-> Upgrade -> Save`), though may not be perfect.
2025-02-06 19:32:32 -08:00
Dermot Duffy faa5ec3eec refactor: Refactor internal error / info message components (#1842) 2025-01-20 15:26:53 -08:00
Dermot Duffy e503f0e429 Initial version of status bar. 2024-08-16 20:08:21 -07:00
Dermot Duffy b08a8f55c3 Fix minor type errors. 2024-03-07 06:46:02 -08:00
Dermot Duffy e12912fa85 Add out-of-the-box Frigate PTZ support. 2023-10-29 14:10:41 -07:00
Dermot Duffy 9a2eb24d35 Rename service_data to data to maintain HA compatibility. 2023-10-08 21:17:57 -07:00
Dermot Duffy f2e230fa81 Add mixxing part of prior commit. 2023-10-03 20:22:56 -07:00
Dermot Duffy 9b72dd8089 Refactor main types.ts . 2023-10-02 22:35:58 -07:00
Dermot Duffy bfdbe70d6c Refactor card.ts substantially and test. 2023-10-02 16:30:05 -07:00
Dermot Duffy fd74c1c855 Add initial support for automations. 2023-05-04 19:54:41 -07:00
Dermot Duffy b10b0e6072 Add native PTZ element. 2022-08-16 21:27:10 -07:00
Dermot Duffy ffa31c05ed Clean lit-plugin warnings 2022-06-19 07:30:08 +00:00
Dermot Duffy 10c2e746b2 Don't re-render the whole card for conditionState. 2022-06-12 17:56:25 -07:00
Dermot Duffy e9dde20a76 Allow the image view to trigger a card resize. 2022-06-11 16:28:59 -07:00
Dermot Duffy 7ebada9aa5 Initial draft of recordings support. 2022-06-04 11:04:38 -07:00
Dermot Duffy 6b30487229 Initial common.js refactor. 2022-05-12 20:42:02 -07:00
Dermot Duffy 47e2f47b58 Update the elements root when the config changes. 2022-05-09 16:34:06 -07:00
Dermot Duffy 28292483fc Add native select entity support. 2022-05-08 15:58:56 -07:00
Dermot Duffy 050399a517 Codereview fixes. 2022-05-01 12:53:30 -07:00
Dermot Duffy dafaae7fe4 First attempt to add state to frigate card conditions. 2022-04-30 21:39:29 -07:00
Dermot Duffy af07624e51 Retire use of @query and use ref univerally. 2022-04-18 19:20:03 -07:00
Dermot Duffy 98e2a89a41 Remove superfluous imports. 2022-04-18 18:08:46 -07:00
Dermot Duffy 22c2b022e7 Avoid use of ExtendedHomeAssistant wherever possible. 2022-04-18 18:04:51 -07:00
Dermot Duffy 70ee05168f Create a means of re-rendering the card based on external state. 2022-02-17 19:34:12 -08:00
Dermot Duffy 93ff4d8445 Initial submenu support. 2021-12-17 18:08:07 -08:00
Dermot Duffy c7506c9ee0 Add menu conditions. 2021-11-14 15:32:25 -08:00
Dermot Duffy fc3033a0fe Add support for card and view actions. 2021-11-13 20:50:11 -08:00
Dermot Duffy 19291ae184 Allow custom actions to control the frigate card. 2021-11-10 07:45:59 -08:00
Dermot Duffy 66ae262654 Implement downloading and correct MediaShow events in carousel. 2021-10-29 16:36:36 -07:00
Dermot Duffy e5b1deb334 Various build extension fixes. 2021-10-24 07:57:28 -07:00
Dermot Duffy 031d9362d6 Fix element rendering. 2021-10-18 22:30:44 -07:00
Dermot Duffy 7f76543bbd Add frigate-card-conditional element to select views. 2021-10-09 23:33:54 -07:00
Dermot Duffy 9a915af390 Better integrate elements into stock HA custom element support. 2021-10-09 15:52:52 -07:00
Dermot Duffy 49455ab741 Add picture elements support. 2021-10-07 21:16:16 -07:00