Ryan Malloy 9ca4da98e8
Some checks are pending
Validate / HACS validation (push) Waiting to run
Validate / Hassfest (push) Waiting to run
panel: clausal chain editor (WHEN/AT/EVERY + AND/OR/THEN)
Multi-record clausal programs are now editable end-to-end. A chain
spans N consecutive slots — head (WHEN/AT/EVERY) + zero-or-more
AND/OR condition records + one-or-more THEN action records — so
"editing" means rewriting the whole run, validating that any
expansion doesn't trample adjacent programs, and clearing any old
slots when the chain shrinks.

H1 — backend:

* programs/get for chains now returns chain_members[] with each
  member's slot + role + raw fields. The editor uses this to seed
  one editable form-row per slot.
* New programs/chain/write command: takes head_slot + head dict +
  conditions[] + actions[], does N sequential download_program
  calls, then clears any old chain slots that fell outside the new
  range. Validates:
    - head_slot + new_len doesn't extend past slot 1500
    - any expansion-into slot not already part of THIS chain is FREE
      (anti-trample: refuse rather than overwrite an adjacent program)
    - at least one THEN action present (empty chain rejected)
  Updates coordinator.data.programs immediately so subsequent list
  calls reflect the edit before the next poll.

H2 — TS helpers:

* AND-record encoding mirrors compact-form cond family bytes
  (0x04 ZONE / 0x08 CTRL / 0x0C TIME / 0x00 OTHER + 0x10+ SEC) but
  with a slightly different bit layout: the family byte lives at
  fields.cond & 0xFF (disk byte 1) and the instance at
  (fields.cond2 >> 8) & 0xFF (disk byte 3). The selector bit is
  family's bit 0x02 instead of cond's 0x0200. decodeAndCondition /
  encodeAndCondition handle both directions; round-trip exact.
* isStructuredAnd helper detects records with OP > 0 (TEMP > N
  comparisons etc.); those render read-only in the chain editor
  with a warning banner.
* emptyAndRecord / emptyOrRecord / emptyThenRecord helpers for
  the add-condition / add-action buttons.

H3 — chain editor UI:

* New _chainDraft state (parallel to _editingDraft for compact form)
  with head + conditions[] + actions[] arrays. Mutation helpers
  preserve immutability via array-copy-then-patch.
* "Edit" button on chain detail now opens the chain editor instead
  of returning early (previous read-only behaviour).
* Three sub-renderers: trigger section dispatches on prog_type
  (WHEN→event-id builder reusing the EVENT helpers, AT→time+days
  reusing TIMED layout, EVERY→single seconds input that packs into
  cond+cond2), conditions section with per-row add/remove (separate
  + AND IF and + OR IF buttons in the legend), actions section with
  per-row add/remove (+ THEN button; at least one action enforced).
* Structured-OP AND records render with an explanatory read-only
  banner and a × button to drop the row entirely — preserves the
  data when the user doesn't touch it, lets them remove it cleanly
  when they want to.
* Each row picks objects via _bucketWithPreserve so out-of-range
  zone/unit/area indices stay safe.

5 new HA integration tests:
* get-chain returns chain_members with correct roles + raw fields
* chain/write in-place rewrite preserves footprint, updates bytes
* chain/write shrink clears the trailing old slots
* chain/write refuses to trample an adjacent program on expansion
* chain/write rejects zero-actions submission

Live screenshot 11-chain-editor.png: state injection into the side
panel (real panel has no chains) shows the editor rendering a sample
WHEN zone-state → AND IF unit ON → 2x THEN action chain with every
control populated and functional.

Full suite: 653 passed, 1 skipped (up from 648, 5 new chain tests).
Frontend bundle: 82 KB minified (up from 63 KB).
2026-05-17 02:09:04 -06:00
..

Omni Programs side panel — frontend

Lit/TypeScript source for the HA side panel registered by websocket.py:async_register_side_panel. The build output (../www/panel.js) is committed so end-users don't need Node installed.

Edit / rebuild

cd custom_components/omni_pca/frontend
npm install         # one-time
npm run build       # one-shot — drops a fresh ../www/panel.js
npm run watch       # rebuild on change (use during HA dev)

The build script (build.mjs) bundles the entry point + Lit + all imports into a single ESM file at ../www/panel.js. Source maps are inlined in --watch mode and stripped in production builds. Output is ~34 KB minified.

Layout

File Purpose
src/omni-panel-programs.ts The custom-element entry point. Defines <omni-panel-programs> (matching the panel_custom registration).
src/token-renderer.ts Token stream → Lit TemplateResult. Each TokenKind gets distinctive styling; REF tokens become buttons that dispatch a click.
src/types.ts TS interfaces mirroring the Phase-B websocket wire shapes. Short keys (k/t/ek/ei/s) match websocket.py:_tokens_to_json.

Wire contract

The panel calls three websocket commands (all defined in ../websocket.py):

  • omni_pca/programs/list — paginated, filterable summaries.
  • omni_pca/programs/get — full structured-English detail for one slot.
  • omni_pca/programs/fire — sends Command.EXECUTE_PROGRAM over the wire.

The frontend doesn't subscribe to push events; live-state badges refresh on a low-frequency poll (REFRESH_MS = 5000). That's a deliberate scope choice — switching to per-entity event subscription is a follow-up if the polling overhead becomes visible on huge installs.