Layout Engine
The engine is a small, HA-agnostic pipeline. Everything that knows about Home Assistant internals is quarantined in HaDomAdapter; the rest works against plain data structures and is unit-testable.
LayoutManager (booted once per page)
├─ HaDomAdapter locate Sections views (ALL Home Assistant coupling lives here)
├─ RuntimeContext screen size, narrow, touch, fullscreen, browser_id, user
├─ RulesEngine pure function: RuntimeContext → LayoutId
└─ Renderer apply the chosen Layout to each located view
└─ Layout { id; matches?; apply(target): Disposable }
├─ StandardLayout no-op (native rendering)
├─ SwipeLayout scroll-snap pager (1 section = 1 page)
├─ CarouselLayout (later)
└─ KioskLayout / TVLayout (later)Layouts are pluggable
A Layout implements a single method, apply(target, context), and returns a Disposable. Switching layouts disposes the previous one — removing any injected <style> and listeners — so there are no leaks and no residue.
Layouts are CSS-first: apply typically injects a scoped stylesheet targeting the located view and returns a teardown handle. JavaScript only orchestrates (page dots, keyboard and gesture navigation, re-evaluation on resize or edit-mode toggle) — it never rebuilds the dashboard.
Runtime rules
RulesEngine is a pure function mapping the current RuntimeContext to a LayoutId. v1 ships built-in defaults only:
- Edit mode → standard (so the native editor keeps working).
- Touch + narrow (phones) → swipe.
- Everything else → standard.
v2 will let Config Entries override these defaults (per device, user, or media query) without YAML.