Ryan Malloy f38777e219
Some checks are pending
Validate / HACS validation (push) Waiting to run
Validate / Hassfest (push) Waiting to run
HA: side panel frontend (Lit/TypeScript) for the program viewer
Phase C of the program viewer. Replaces the "panel coming soon" stub
with a real Lit-based side panel that consumes the Phase-B websocket
commands.

Layout (top to bottom):

* Header — title + total/filtered count
* Filter bar — search box (substring match), trigger-type chips
  (TIMED / EVENT / YEARLY / WHEN / AT / EVERY / REMARK), a clearable
  "filtering on <ref>" pill when an entity filter is active
* Two-column body: program list on the left, slide-in detail panel
  on the right. Collapses to single-column on `narrow` view.

The program list renders one row per program (or per chain head, for
clausal multi-record programs). Each row carries the slot number,
the rendered one-line summary token stream, and meta pills for
trigger type / condition count / multi-action count.

The detail panel renders the full structured-English token stream
inside a styled <pre>. A "Fire now" button calls
``omni_pca/programs/fire`` over the wire — the panel actually
runs the program. For chain detail the spanned slot range is shown
underneath.

REF tokens are rendered as `<button>` elements that click to filter
the list to "programs that mention this entity" — the most useful
navigational affordance for the "why is this happening?" use case.

Live-state badges (SECURE / NOT READY / ON 60% / Away / 72°F / …) are
appended to REF tokens via the Phase-B coordinator-backed
StateResolver. The panel polls ``programs/list`` every 5 seconds to
refresh badges; switching to push-event subscriptions is a follow-up
when polling overhead becomes visible.

Theming uses HA's standard CSS variables (--primary-color,
--card-background-color, --divider-color, etc.) so the panel inherits
the user's HA theme automatically.

Build pipeline:

* TypeScript source under ``custom_components/omni_pca/frontend/src/``
* esbuild bundles entry → ESM in one self-contained file
* Output at ``custom_components/omni_pca/www/panel.js`` (~34 KB
  minified) is committed so end-users don't need Node installed
* ``npm run watch`` for HA-dev-time iteration
* tsconfig has strict mode + noUnusedLocals; bundle currently
  type-checks clean

Manifest declares deps on ``http`` and ``websocket_api``; ``frontend``
and ``panel_custom`` are loaded opportunistically (they require
``hass_frontend`` which the test harness doesn't ship — keeping them
out of the manifest deps keeps tests green).

Full suite: 634 passed, 1 skipped (no test changes; the integration
side hasn't moved since Phase B).
2026-05-15 21:21:41 -06:00

45 lines
1.5 KiB
JavaScript

// Bundle the omni_pca side panel into a single ESM file the HA static
// path serves at /api/omni_pca/panel.js.
//
// Usage:
// node build.mjs # one-shot production build
// node build.mjs --watch # rebuild on source change
//
// Output is intentionally placed at ../www/panel.js so the Python side
// (websocket.py:async_register_side_panel) finds it without extra
// configuration. The frontend dir + the Python integration sit in the
// same custom_components/omni_pca/ tree so end-users just install the
// integration; no separate HACS package needed.
import { build, context } from "esbuild";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
const __dirname = dirname(fileURLToPath(import.meta.url));
const watch = process.argv.includes("--watch");
const opts = {
entryPoints: [resolve(__dirname, "src/omni-panel-programs.ts")],
bundle: true,
format: "esm",
target: "es2022",
minify: !watch,
sourcemap: watch ? "inline" : false,
outfile: resolve(__dirname, "../www/panel.js"),
// Lit ships its own ESM build; bundle it inline so the panel is a
// single self-contained file (matches how HACS-distributed cards work).
loader: { ".ts": "ts" },
banner: {
js: "// omni_pca side panel — generated by frontend/build.mjs. Edit src/, not this file.",
},
};
if (watch) {
const ctx = await context(opts);
await ctx.watch();
console.log("watching for changes…");
} else {
await build(opts);
console.log("built ->", opts.outfile);
}