Files
home-information/docs/dev
Tony C 3c33cf29cf EntityStatusPanel framework, dispatcher, and three pilot panels (#328)
* Add EntityStatusPanel framework: dispatch tag, presentation helper, JS hook

Lays the foundation for per-EntityType panels in the three display contexts
(modal, list, grid). No panels yet — just the plumbing the panels will use.

The render_entity_status_panel template tag resolves a panel along a three-
step chain: panel's context-specific template, panel's required default
(modal.html), framework fallback. Framework fallbacks are guaranteed to
exist and currently delegate to the existing flat-list / EntityStatus
default partials so behavior is preserved for every EntityType without a
panel.

The panel_state template tag looks up an EntityState by EntityStateRole
for panel templates that need to reach a specific state by semantic
purpose (e.g., a thermostat panel reaching for its current-temperature
state vs. the setpoint). Walks the entity's own states then delegations;
own-states win on role collision.

The JS dispatcher in entity_state_status.js gains two small additions:

  - The status forwarding selector relaxes from div[status] to [status]
    so panels can use any element tag for the status carrier.
  - After the existing apply pass, registered EntityStatusPanel handlers
    are notified with the statusMap. Panels that need to react beyond
    CSS-driven variant switching register a handler via
    Hi.statePanels.register; panels that don't pay nothing.

Dispatch is wired into the three call sites: EntityStatusView modal body,
collection list card, collection grid card. The legacy entity_status_<type>
modal templates and the collection list/grid is_cameras branches are no
longer in the new resolution chain but stay in place — their migration is
deferred to Phase 3 and Phase 6 respectively.

Tests cover the dispatch fallback chain (per-context resolution, panel
overrides, modal-as-default for list/grid, unsupported context errors)
and the panel_state role-lookup (case-insensitive match, missing role,
delegated states, own-state wins on collision).

* Add smoke detector EntityStatusPanel; rework dispatch tag to take entity_status_data

The smoke detector panel is the CSS-only reference panel. Templates emit
the four visual variants (clear / alarm / recent / past) as separate
elements with class-modifier suffixes; CSS keyed on the panel root's
``status`` attribute shows the one matching the current state. Two
status vocabularies land on the root over a panel's lifetime — the raw
sensor value (smoke_clear / smoke_detected) on initial server render,
and the decay-aware value (idle / active / recent / past) once the first
polling refresh lands. CSS comma-groups them so both render the right
variant.

The dispatch tag's signature changes to take ``entity_status_data``
rather than ``entity``. The tag calls ``entity_status_data.to_template_
context()`` and merges those fields into the panel template's context,
so panels use top-level names (entity, state_status_data_list,
state_status_data_by_role, etc.) regardless of whether the dispatch fires
from the modal, list card, or grid card call site. This resolves the
prior asymmetry where the modal context had fields unpacked at top level
but collection card contexts only had ``entity_status_data`` as a single
variable.

EntityStatusData gains a ``state_status_data_by_role`` property — the
role-keyed lookup panel templates use to pull a specific state by
semantic role (e.g., ``state_status_data_by_role.smoke`` on a smoke
detector, ``state_status_data_by_role.battery_level`` for the battery
auxiliary). The dispatch tag exposes it via ``to_template_context()``,
which now also self-references ``entity_status_data: self`` so the
modal's own template can pass the dataclass to the dispatch tag.

Pipeline registers the smoke detector CSS in the ``css_head`` bundle.
Per-panel asset auto-loading is deferred per the v1 spec.

* Add camera EntityStatusPanel and framework-level identifying chrome

Camera panel composes existing video-stream and state-status partials:
the existing entity_video_stream_w_link.html for the live frame
(click-through to the full-page video pane preserved), and the existing
entity_state_status_list.html for any controllers/sensors the entity
exposes (so ZoneMinder Function dropdown and the like render through
the established controller_data machinery). No new JS — connection
management, MJPEG rendering, and integration-gateway lookup all stay
where they live. The is_cameras collection branches are unrelated
(montage view) and stay in place.

EntityStatus modal chrome reworked to provide identifying information
at the framework level rather than each panel template repeating it.
Modal title carries the entity name. A new subheader strip inside the
modal body shows entity-type icon + label on the left, integration
logo centered (when present), and the "Status" modal-type label on
the right. Panels render their body below this strip and don't
duplicate any of this identifying chrome. The smoke detector and
camera panel templates lose their per-panel headers accordingly.

* Tighten polling-update scope and make the flat-list fallback a panel

Polling-update dispatch in entity_state_status.js previously expanded its
scope from the matched CSS-class element to its descendants when looking
for ``display-text`` / ``[status]`` markers. That worked for the flat-
list partials' wrapper-with-class + child-with-attr structure but bled
across state boundaries when a state's CSS class anchored a panel root
containing other states' display elements.

Tightens the contract: an element receives a polling update only when
it has BOTH the matching CSS class AND the marker attribute (``status``,
``display-text``, etc.) on the same element. No descendant propagation.
``filter('[attr]')`` replaces ``find('[attr]').addBack(...)``; the
``else if (attrName == 'status') { find('div[status]') }`` legacy path
is gone.

The sensor flat-list templates that previously wrapped the value div in
a class-bearing wrapper are restructured: the value div carries the
class itself, and the outer wrapper is removed.

entity_state_status.js absorbs the universally-useful pieces of the old
controllers.js (active-slider tracking, value-to-element dispatch,
slider-display sync, checkbox value coercion, slider drag input mirror)
and combines attribute / display / controller-value application into
one pass per CSS class — the prior two-stage apply with a separate
``Hi.controllers.applyValueMap`` callout dropped.

The flat-list-specific bits that remained in controllers.js — the
``.on-off-control`` / ``.status-text`` caption mirror and the
continuous-slider preset-button click handler — move into a new
``state_panels/fallback/fallback.js`` and register with the post-apply
panel hook like any other panel JS. The fallback is now its own panel
(matching the existing fallback templates in state_panels/fallback/).

controllers.js deleted; pipeline updated accordingly (entity_state_
status.js promoted into the always-loaded ``js_before_content`` bundle
so the fallback panel's ``Hi.statePanels.register`` call has the
framework's panel-handler registry in scope).

* Self-describing polling dispatcher and thermostat panel

Dispatcher rewritten around a data-state-id anchor plus per-element
declaration attributes (data-status, data-display-text/magnitude/unit,
data-controller-value, data-svg-style). Replaces the class-based
join that forced authors to fuse a CSS class and an update marker
onto the same element and shared the status attribute namespace with
the global div[status] paint. Each element now opts in directly;
no descendant traversal, no propagation leaks.

Server-side statusMap keyed by entity_state.id with nested
{status, controller, display, svg_style}. SVG icons opt in via
data-status (singular push); SVG paths use data-svg-style (full
bundle) so each element only receives the attributes it rendered.

Global div[status] rule rescoped to .hi-status-display[status],
freeing the status attribute for panel use. EntityStateDisplayData
delegates raw accessors via __getattr__ so mainline templates that
took raw status data also accept the display wrapper.

Panel framework adds registerInit alongside registerUpdate. Init
handlers fire at jQuery ready and after async content insertion;
antinode gets a dedicated afterModalRender hook so JSON-delivered
modals get their init pass once the modal is in the DOM (the
existing afterAsyncRender stays at its earlier position to keep
scroll-bar restore ahead of resetScrollbar/scrollTo).

Adds thermostat EntityStatusPanel (modal/list/grid) with SVG dial
markers positioned via the registered panel JS.

Renames monitor/transient_models.py to status_data.py and
status_display_data.py to display_data.py for symmetric naming;
EntityDisplayData wraps raw EntityStatusData with display
projections (CollectionData and EntityStatusView adopt it).

* Document the icon vs path polling-update asymmetry

SVG icons opt into polling updates via data-status while paths use
data-svg-style; an existing bug fix made the choice mechanical but
the rationale lived only in conversation. Adds a short subsection
under Client-Server Status Updates explaining why icons can't
safely receive the full attribute bundle (SVG inheritance fights
child-specific styling) and why paths can (single element with a
Python-parameterized palette).

* Consolidate fallback panel templates into the panel directory

The EntityStatusPanel fallback was structured as three thin
wrappers that included historical pre-framework templates living
elsewhere. The asymmetry forced new readers to chase indirection
to understand what the fallback actually renders, and left
chrome-rendering duplicated between the legacy default body and
the modal framework subheader.

Inlines the fallback's modal/list/grid templates and moves the
flat-list partials (now state_list.html and state_row.html) into
state_panels/fallback/, since fallback is where those partials
were first authored. Camera and thermostat panels — which embed
the flat list inside their own modal layouts — reference the
partials from their new home.

Drops the duplicated identifying chrome (entity name, integration
logo, type label) from the fallback modal body; the framework's
subheader block in entity_status.html already renders it.

Deletes orphan legacy templates: entity_status_default.html, the
unused per-type wrappers entity_status_thermostat.html and
entity_status_ceiling_fan.html, and the collection-card list/row
pair (collection/panes/entity_state_{list,row}.html) which had
diverged from entity/panes/entity_state_status_{list,row}.html on
short-name vs full-name with no real justification — both contexts
display the entity name in surrounding chrome, so the short-name
form is correct in both.

* Remove dead in_modal_context flag from controller form rendering

``in_modal_context`` was originally a hint to controller_data.html
about whether to emit ``data-stay-in-modal="true"`` (which keeps
the entity status modal open across control submissions) and a
hidden ``response_context`` input the server read back to preserve
the flag across the form-replace round trip.

The configurability was already dead: ``controller_data_row.html``
hardcoded ``in_modal_context=True`` at the only template boundary
where the value reached ``controller_data.html``, so every
controller form already emitted both signals. Outside a modal,
``data-stay-in-modal`` is a no-op because antinode's modal-close
check gracefully handles the no-modal case.

Drops the flag everywhere: hardcodes ``data-stay-in-modal="true"``
on the controller form, removes the hidden ``response_context``
input and its server-side parse, drops ``in_modal_context`` from
``controller_data_response``'s signature, and strips the unused
``in_modal_context=True`` from the panel templates that propagated
it (camera/modal, thermostat/modal, fallback/{modal,list,grid}).
No observable behavior change.

* Reorganize and reconcile entity display frontend docs

The three existing docs (entity-status-display, entity-status-panels,
entity-visual-configuration) overlapped on the polling-update
contract while none gave a top-level mental model. The two older
docs also contained fictional code samples that pre-dated the
data-state-id dispatcher refactor and actively misled readers.

Adds entity-display-overview as a one-page architecture entry
point: the sensor-to-pixel flow, the four display surfaces
(LocationView SVG icons, paths, status modal, collection cards),
the polling contract as connective tissue, and a topic-keyed
index into the focused docs.

Rewrites entity-status-display as the polling-update contract
reference: server payload shape, the data-state-id anchor and
declaration grammar, status-value vocabulary, color palette, and
the icon-vs-path asymmetry. Removes the fictional CSS-class
mapping examples and the fictional thermostat workflow that
pre-dated the dispatcher refactor.

Trims entity-status-panels' polling-contract section to defer to
the contract doc rather than restating it.

Slims entity-visual-configuration to its accurate core: SVG icon
and path asset authoring, EntityStyle registration, and per-type
StatusStyle binding. Removes the fictional Template Integration
section and the two fictional workflow walkthroughs that invoked
methods (get_status_css_classes, get_path_styling) that don't
exist.

Each doc now cross-links to all three others.

* Surface integration health and improve controller error chrome

Adds two failure-mode UX improvements to the entity status modal:

1. Integration health banner. When an entity's owning integration
   reports WARNING / ERROR / DISABLED, the modal renders an alert at
   the top of its body indicating the integration's status and that
   displayed values may be stale and controls may not respond. The
   banner is a snapshot at modal-open time (no live tracking); it
   renders nothing for healthy integrations.

   Implemented as a reusable inclusion tag
   ``integration_health_banner`` in the integrations app, so any
   surface that depends on an integration's data can drop in the
   banner with a single template line plus an optional context
   message describing how the degraded state affects that surface.

2. Controller error chrome. Replaces the bullet-list red-text error
   block on failed controller actions with a Bootstrap alert-danger
   that uses an icon, a compact small-text body, and one line per
   error message. The styling matches the rest of the application's
   error UI; the message content channel is unchanged.

* Use exclamation-circle for critical health status icons

Aligns the icon for FAILING / critical health status with the
existing icon for ERROR / UNAVAILABLE — both states are attention
signals, and a shared exclamation-circle glyph reads more
consistently across the health-status UI than the previous
times-circle. Updates ApiHealthStatus.status_icon,
HealthStatus.status_icon, and the test-UI page that mirrors the
critical-state icon.

* Fix Firefox video stream connection leaks via service worker and lifecycle hooks

Three reinforcing changes that together resolve Firefox failures when
loading pages with multiple MJPEG video streams (live camera streams
and event-video playback). Chrome was unaffected; Firefox's service
worker handling of concurrent multipart/x-mixed-replace responses
combined with no explicit teardown on async content swap caused
connections to leak across page navigations and exhaust the per-host
pool.

Service worker scope. The fetch handler now intercepts only the
pre-cached static assets list, returning early for everything else.
MJPEG streams, polling, and dynamic pages flow through the browser's
native fetch path, avoiding the multipart-via-SW edge that Firefox
mishandles when multiple streams are concurrent.

Antinode before-render hook. Adds addBeforeAsyncRenderFunction
mirroring the existing after-render hook. Antinode fires it with the
outgoing $target before each HTML content swap, giving subscribers
the chance to clean up resources tied to elements about to be
orphaned. Generic; antinode no longer knows about video.

Video connection manager rewrite. Replaces the single-current +
small-previous-cache design (which used querySelector and could only
ever see one element) with a Set tracking every <img> opted in via
data-video-stream. Registers a beforeAsyncRender callback that
force-closes streams in the outgoing subtree by swapping src to a
transparent GIF, terminating the fetch before the browser orphans
the element. Reconciles on each afterAsyncRender / afterModalRender
to keep the set honest. Adds the marker to all six stream <img>
templates: live stream, event playback, alert pane and modal video
elements, sensor history modal.

* Address post-review feedback: lifecycle, caching, SW scope, hook isolation

Four follow-up fixes from the pre-PR review pass:

1. Modal-dismiss video stream leak. Renamed antinode's
   ``addBeforeAsyncRenderFunction`` to ``addBeforeContentRemovalFunction``
   so the name reflects what it really fires for — any subtree about to
   be detached from the DOM by an antinode-driven operation. Added a
   second call site in ``handleModalHiddenEvent`` before the modal is
   removed, so VideoConnectionManager force-closes streams on modal
   dismissal in addition to HTML content swap. Antinode has no direct
   reference to the manager; both call sites just invoke registered
   callbacks.

2. Antinode hook isolation. The three lifecycle loops
   (``beforeContentRemoval``, ``afterAsyncRender``, ``afterModalRender``)
   now wrap each callback invocation in try/catch and log on error.
   Matches the pattern already in place in entity_state_status.js'
   panel-handler loops. A throwing handler logs and yields rather than
   skipping siblings or bubbling into antinode's caller.

3. EntityDisplayData per-state wrapping memoization. Adds a
   ``state_display_data_map`` field initialized in ``__post_init__``
   that wraps each contained state in ``EntityStateDisplayData``
   exactly once. The two projection properties
   (``state_status_data_list``, ``state_status_data_by_role``) now
   look up wrappers from the map. Each per-state cost (the
   ConsoleConverterHelper lookup and ``_get_svg_status_style`` dispatch)
   is paid once per render even when both projections are touched
   (the typical ``to_template_context()`` path).

4. Service worker rewrite. The fetch handler now intercepts only
   paths under Django's STATIC_URL prefix; the enumerated
   STATIC_ASSETS list goes away entirely so the SW doesn't need
   manual maintenance as the static asset surface grows. Cache-first
   with on-demand population on miss; bumped CACHE_VERSION to wipe
   any stale state from the previous SW. STATIC_URL is exposed to all
   templates via the existing constants_context processor.
2026-05-14 20:27:56 -05:00
..
2026-04-20 10:05:04 -05:00
2025-09-25 18:31:38 -05:00
2026-04-20 10:05:04 -05:00

Home Information Logo

Developer Documentation

See the markdown files in this directory for various developer-related documentation.

The code is the best documentation for lower-level details, but there are some higher-level concepts that are useful to help orient developers. This is the place for that high-level, developer-specific documentation.