diff --git a/README.md b/README.md
index 9c29d69e..dbde29a2 100755
--- a/README.md
+++ b/README.md
@@ -22,6 +22,7 @@ Live, personal Home Assistant configuration shared for **browsing and inspiratio
- You are here: `/` (root repo guide)
- [Blog](https://www.vcloudinfo.com) | [Issues](https://github.com/CCOSTAN/Home-AssistantConfig/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) | [Diagram](config/www/custom_ui/floorplan/images/branding/Bear-Stone-Docker-Diagram.jpg) | [YouTube](https://youtube.com/vCloudInfo)
- Config readmes: [Config index](config/README.md) | [Packages](config/packages/README.md) | [Automations](config/automation/README.md) | [Scripts](config/script/README.md) | [Scenes](config/scene/README.md) | [Sounds](config/sounds/README.md) | [Package triggers](config/packages/triggers/README.md)
+- Codex skills (optional): [codex_skills](codex_skills)

diff --git a/codex_skills/README.md b/codex_skills/README.md
new file mode 100644
index 00000000..11280fd7
--- /dev/null
+++ b/codex_skills/README.md
@@ -0,0 +1,20 @@
+
+ Codex Skills
+
+
+Optional Codex skills stored in-repo so they can be shared with the community.
+
+### Quick navigation
+- You are here: `codex_skills/`
+- [Repo overview](../README.md) | [Dashboards](../config/dashboards/README.md) | [Issues](https://github.com/CCOSTAN/Home-AssistantConfig/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)
+
+## Skills
+
+- `homeassistant-dashboard-designer/`
+ - A constrained, button-card-first Home Assistant Lovelace dashboard design system + a small YAML lint helper.
+
+### Notes
+- These skills are documentation + helper scripts only; they should not contain tokens, secrets, or environment-specific credentials.
+- Codex loads skills from your local Codex home. See each skill's README for install steps.
+
+
diff --git a/codex_skills/homeassistant-dashboard-designer/README.md b/codex_skills/homeassistant-dashboard-designer/README.md
new file mode 100644
index 00000000..21c370dc
--- /dev/null
+++ b/codex_skills/homeassistant-dashboard-designer/README.md
@@ -0,0 +1,57 @@
+
+ Home Assistant Dashboard Designer (Codex Skill)
+
+
+This directory contains the `homeassistant-dashboard-designer` Codex skill, stored in-repo so it can be shared with the community.
+
+### Quick navigation
+- You are here: `codex_skills/homeassistant-dashboard-designer/`
+- [Repo overview](../../README.md) | [Codex skills](../README.md) | [Dashboards](../../config/dashboards/README.md) | [Issues](https://github.com/CCOSTAN/Home-AssistantConfig/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)
+
+## What This Skill Does
+
+- Enforces a constrained Lovelace design system (button-card first, minimal card-mod, grid/vertical-stack layout rules).
+- Encourages centralized templates and deterministic YAML output.
+- Treats Stitch MCP output as *inspiration only* and translates ideas into safe Lovelace YAML.
+
+Skill source of truth inside this folder:
+- `SKILL.md`
+- `agents/openai.yaml`
+- `references/dashboard_rules.md`
+- `scripts/validate_lovelace_view.py`
+
+## Install (Local Codex)
+
+Codex loads skills from your local Codex skills directory.
+
+1. Copy this folder into your Codex skills directory as:
+ - `~/.codex/skills/homeassistant-dashboard-designer/` (Linux/macOS)
+ - `%USERPROFILE%\\.codex\\skills\\homeassistant-dashboard-designer\\` (Windows)
+2. Restart your Codex session/editor so it re-indexes skills.
+
+## Stitch MCP Install (Design Inspiration)
+
+Stitch is optional and used only for design inspiration. To enable it, add a Stitch MCP server entry to your Codex config.
+
+1. Set an environment variable with your API key:
+ - `STITCH_API_KEY=`
+2. Add this to your `~/.codex/config.toml`:
+
+```toml
+[mcp_servers.stitch]
+url = "https://stitch.googleapis.com/mcp"
+env_http_headers = { "X-Goog-Api-Key" = "STITCH_API_KEY" }
+http_headers = { "Accept" = "application/json" }
+```
+
+## Usage
+
+Invoke in chat:
+- `$homeassistant-dashboard-designer`
+
+Then provide the structured intent block described in `SKILL.md` (dashboard intent, view name, entity map, and layout constraints).
+
+### Notes
+- This skill intentionally contains no secrets. Configure MCP credentials via environment variables in your local Codex setup.
+
+
diff --git a/codex_skills/homeassistant-dashboard-designer/SKILL.md b/codex_skills/homeassistant-dashboard-designer/SKILL.md
new file mode 100644
index 00000000..f719ce47
--- /dev/null
+++ b/codex_skills/homeassistant-dashboard-designer/SKILL.md
@@ -0,0 +1,143 @@
+---
+name: homeassistant-dashboard-designer
+description: "Design, update, and refactor Home Assistant Lovelace dashboards (YAML views/partials) with a constrained, machine-safe design system: button-card-first structure, minimal card-mod styling, optional flex-horseshoe + mini-graph telemetry, strict grid/vertical-stack layout rules, centralized templates, deterministic ordering, and config validation. Use for Home Assistant dashboard work (especially config/dashboards/**), when refactoring views, adding infra/home/energy/environment panels, or translating Stitch design inspiration into safe Lovelace YAML."
+---
+
+# Home Assistant Dashboard Designer
+
+Design dashboards as systems: predictable structure, reusable templates, minimal drift. Treat Stitch as inspiration only; translate to safe Lovelace YAML.
+
+## Input Contract (Ask For This If Missing)
+
+Request structured intent, not raw YAML:
+
+```yaml
+dashboard_type: infra # infra|home|energy|environment
+view_name: "Infrastructure Overview"
+target_paths:
+ dashboard_yaml: "config/dashboards/infrastructure/dashboard.yaml"
+ view_yaml: "config/dashboards/infrastructure/views/07_docker_containers.yaml"
+entities:
+ - name: "Proxmox01"
+ cpu: "sensor.proxmox01_cpu"
+ memory: "sensor.proxmox01_mem"
+constraints:
+ desktop_columns: 4
+ mobile_columns: 2
+notes:
+ - "Preserve existing templates; minimize diff."
+```
+
+If updating an existing view, also ask:
+
+- Which view file (single view dict) is the source of truth?
+- Any "don't touch" areas/templates?
+- Any fallback cards already in use that must remain?
+
+## Output Contract (Always Produce)
+
+- One valid Lovelace view (single view dict)
+- Deterministic ordering (stable ordering of cards/sections)
+- Section comments (short, consistent)
+- Zero per-card styling variance (no one-off styles; use templates/snippets)
+- Fallback usage explicitly justified (YAML comments adjacent to the fallback card)
+
+## Card Priority Model
+
+Attempt Tier 1 first. Use Tier 2 only if Tier 1 cannot express the requirement safely.
+
+Tier 1 (primary):
+- `custom:button-card` (structure + tiles + headers + containers)
+- `card-mod` (shared/global polish only; shared snippets OK)
+- `custom:flex-horseshoe-card` (single-metric radial telemetry)
+- `custom:mini-graph-card` (single-metric trends)
+
+Tier 2 (fallback, must justify in comments):
+- `entities`, `markdown`, `history-graph`, `conditional`
+- `iframe` (last resort)
+
+## Layout Rules (Hard Constraints)
+
+- Allowed layout containers: `grid`, `vertical-stack`
+- Maximum nesting depth: 2 (example: `grid` -> `vertical-stack` -> cards)
+- No `horizontal-stack` inside grid cells (prefer: more grid columns, or vertical-stack)
+- No freeform positioning
+- No layout logic embedded in `card-mod`
+
+Note: If the repo/view uses Home Assistant `type: sections`, treat `sections` as the top-level structure and enforce the same container rules inside each section (sections should contain only `grid`/`vertical-stack` cards and their children).
+
+## Design System Rules (Implementation Discipline)
+
+### Button Card (Primary Renderer)
+
+- Use `custom:button-card` as the structural primitive.
+- Templates required. Do not inline per-card `styles:`; keep styling inside centralized templates/snippets.
+- Logic via variables or template JS only (avoid duplicating logic per-card).
+
+### card-mod (Styling Constraint Layer)
+
+- Allowed only for shared/global polish:
+ - padding normalization
+ - border radius control
+ - shadow suppression
+ - typography consistency
+- No entity-specific styling
+- No per-card visual experimentation
+
+### Flex Horseshoe Card
+
+- One primary metric per card.
+- Explicit color thresholds.
+- No mixed units.
+
+### Mini Graph Card
+
+- One metric per graph.
+- Consistent time windows per dashboard.
+- Minimal legends (only when required).
+
+## Template Architecture (Required)
+
+- Views reference templates only (for `custom:button-card`: every instance must specify `template:`).
+- Templates accept variables; views pass variables.
+- Do not create new templates unless explicitly instructed.
+
+Repo reality check (important):
+- If the repo already has established templates (for example, Infrastructure uses `bearstone_*`), use what exists.
+- If the spec's "required template categories" are not present, map to the closest existing template and call out the gap, or ask for explicit permission to add templates.
+
+## Stitch MCP (Inspiration Only)
+
+Use Stitch only to learn:
+- visual hierarchy (headers, grouping, density)
+- spacing rhythm (padding/gaps)
+- grouping patterns (panels, sections)
+
+Never treat Stitch output as implementation code.
+
+When prompting Stitch, include these constraints verbatim:
+
+```
+Grid-only layout. No absolute positioning. No JS frameworks. No custom HTML/CSS outside card-mod.
+Card types limited to: custom:button-card, card-mod, custom:flex-horseshoe-card, custom:mini-graph-card, and HA core fallback cards only if unavoidable.
+Max layout nesting depth: 2. No horizontal-stack inside grid cells.
+```
+
+## Workflow (Do This In Order)
+
+1. Read the target dashboard/view/partials/templates to understand existing patterns and avoid drift.
+2. Determine intent: `infra` (NOC), `home`, `energy`, or `environment`. Keep one intent per view.
+3. Validate entities and services before editing (prefer Home Assistant MCP live context; otherwise ask user to confirm entity IDs).
+4. Draft layout with constraints: a top-level `grid` and optional `vertical-stack` groups.
+5. Implement using Tier 1 cards first; reuse existing templates; avoid one-off styles.
+6. If fallback cards are necessary, add an inline comment explaining why Tier 1 cannot satisfy the requirement.
+7. Validate:
+ - Run Home Assistant config validation (`ha core check` or `homeassistant --script check_config`) when available.
+ - Optionally run `scripts/validate_lovelace_view.py` from this skill against the changed view file to catch violations early.
+8. Failure behavior:
+ - If requirements can't be met: state the violated rule and propose a compliant alternative.
+ - If validation fails: stop, surface the error output, and propose corrected YAML. Do not leave invalid config applied.
+
+## References
+
+- Read `references/dashboard_rules.md` when you need the full constraint set and repo-specific mapping notes.
diff --git a/codex_skills/homeassistant-dashboard-designer/agents/openai.yaml b/codex_skills/homeassistant-dashboard-designer/agents/openai.yaml
new file mode 100644
index 00000000..9849e4ba
--- /dev/null
+++ b/codex_skills/homeassistant-dashboard-designer/agents/openai.yaml
@@ -0,0 +1,4 @@
+interface:
+ display_name: "Home Assistant Dashboard Designer"
+ short_description: "Design/refactor HA Lovelace dashboards safely"
+ default_prompt: "Use $homeassistant-dashboard-designer to design, update, or refactor a Home Assistant Lovelace dashboard view using button-card templates + safe grid/stack layouts."
diff --git a/codex_skills/homeassistant-dashboard-designer/references/dashboard_rules.md b/codex_skills/homeassistant-dashboard-designer/references/dashboard_rules.md
new file mode 100644
index 00000000..6173e4e4
--- /dev/null
+++ b/codex_skills/homeassistant-dashboard-designer/references/dashboard_rules.md
@@ -0,0 +1,119 @@
+# Dashboard Rules (Full)
+
+This file is reference material for $homeassistant-dashboard-designer. Prefer following the workflow in `../SKILL.md`, and read this only when you need details.
+
+## Card Priority Model
+
+Tier 1 (primary, attempt first):
+- `custom:button-card`
+- `card-mod` (shared/global styling only)
+- `custom:flex-horseshoe-card`
+- `custom:mini-graph-card`
+
+Tier 2 (fallback, only when Tier 1 cannot satisfy the requirement; justify inline):
+- `entities`
+- `markdown`
+- `history-graph`
+- `conditional`
+- `iframe` (last resort)
+
+## Layout Rules (Hard Constraints)
+
+- Allowed layout containers: `grid`, `vertical-stack`
+- Maximum nesting depth: 2
+- No `horizontal-stack` inside grid cells
+- No freeform positioning
+- No layout logic embedded in `card-mod`
+
+Sections-mode note:
+- If a view uses `type: sections`, treat `sections` as the top-level structure and enforce the same container rules inside each section.
+
+## Template Architecture (Required)
+
+Intent: centralize visuals and logic into templates; views pass only variables and remain uniform.
+
+Rules:
+- `custom:button-card` must use templates (no one-off per-card styles).
+- Templates accept variables.
+- Do not hardcode entity IDs inside templates.
+- Do not create new templates without explicit instruction.
+
+Reality note for CCOSTAN/Home-AssistantConfig:
+- Infrastructure templates already exist in:
+ - `config/dashboards/infrastructure/templates/button_card_templates.yaml`
+- Existing template naming uses `bearstone_*` and includes styling inside templates (this is OK).
+- When asked to implement new conceptual categories (e.g., "status_pill"), first map to existing templates (e.g., `bearstone_infra_chip*`). If no suitable match exists, ask before adding templates.
+
+## Design System (Discipline)
+
+### Button Card
+
+Use it for:
+- Infrastructure tiles
+- Status pills
+- Section headers
+- Control footers
+- Card containers
+
+Rules:
+- Templates required.
+- Avoid per-card `styles:` keys.
+- Avoid per-card `card_mod:` blocks.
+
+### card-mod
+
+Allowed only for shared/global polish:
+- padding normalization
+- border radius control
+- shadow suppression
+- typography consistency
+
+Not allowed:
+- entity-specific styling
+- per-card visual experimentation
+
+### Flex Horseshoe Card
+
+Use it for:
+- Temperature
+- Battery
+- Load
+- Utilization
+- Percentage-based metrics
+
+Rules:
+- One primary metric per card.
+- Explicit color thresholds.
+- No mixed units.
+
+### Mini Graph Card
+
+Rules:
+- One metric per graph.
+- Consistent time windows per dashboard (keep uniform unless user asks otherwise).
+- Minimal legends (only if required for interpretation).
+
+## Stitch MCP Integration (Inspiration Only)
+
+Stitch is advisory, never authoritative.
+
+When using Stitch:
+- Provide feasibility constraints (cards + layout)
+- Use outputs only to inform hierarchy/grouping/density/spacing
+- Translate into approved Lovelace YAML
+
+Required constraints to include in Stitch prompt:
+
+```
+Grid-only layout. No absolute positioning. No JS frameworks. No custom HTML/CSS outside card-mod.
+Card types limited to: custom:button-card, card-mod, custom:flex-horseshoe-card, custom:mini-graph-card, and HA core fallback cards only if unavoidable.
+Max layout nesting depth: 2. No horizontal-stack inside grid cells.
+```
+
+## Repo-Specific Dashboard YAML Rules (config/dashboards/**)
+
+If working in this repo's `config/dashboards/` tree:
+- Do not edit `config/.storage` (runtime state).
+- Includes must use absolute container paths starting with `/config/`.
+- Views are one file per view, and the dashboard file uses `!include_dir_list`.
+- Files under `config/dashboards/**/*.yaml` must include the standard `@CCOSTAN` header block.
diff --git a/codex_skills/homeassistant-dashboard-designer/scripts/validate_lovelace_view.py b/codex_skills/homeassistant-dashboard-designer/scripts/validate_lovelace_view.py
new file mode 100644
index 00000000..ab42f33d
--- /dev/null
+++ b/codex_skills/homeassistant-dashboard-designer/scripts/validate_lovelace_view.py
@@ -0,0 +1,273 @@
+#!/usr/bin/env python
+"""
+Lightweight validator for Lovelace view YAML files.
+
+Goal: catch common violations of the homeassistant-dashboard-designer constraints
+before Home Assistant reloads.
+
+This is NOT a full Lovelace schema validator.
+"""
+
+from __future__ import annotations
+
+import argparse
+import sys
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any
+
+import yaml
+
+
+class _Tagged(str):
+ pass
+
+
+class _Loader(yaml.SafeLoader):
+ pass
+
+
+def _construct_undefined(loader: _Loader, node: yaml.Node) -> Any:
+ # Preserve unknown tags (e.g., !include, !include_dir_list) as opaque strings.
+ if isinstance(node, yaml.ScalarNode):
+ return _Tagged(f"{node.tag} {node.value}")
+ if isinstance(node, yaml.SequenceNode):
+ seq = loader.construct_sequence(node)
+ return _Tagged(f"{node.tag} {seq!r}")
+ if isinstance(node, yaml.MappingNode):
+ mapping = loader.construct_mapping(node)
+ return _Tagged(f"{node.tag} {mapping!r}")
+ return _Tagged(f"{node.tag}")
+
+
+_Loader.add_constructor(None, _construct_undefined) # type: ignore[arg-type]
+
+
+@dataclass(frozen=True)
+class Finding:
+ level: str # "ERROR" | "WARN"
+ path: str
+ message: str
+
+
+ALLOWED_LAYOUT = {"grid", "vertical-stack"}
+DISALLOWED_CARD_TYPES = {"horizontal-stack"}
+
+# Cards allowed without justification (design-system tiering is enforced in SKILL.md; this is a lint).
+ALLOWED_CARD_TYPES = {
+ "grid",
+ "vertical-stack",
+ "custom:button-card",
+ "custom:flex-horseshoe-card",
+ "custom:mini-graph-card",
+ # Tier-2 fallbacks (validator does not enforce justification comments).
+ "entities",
+ "markdown",
+ "history-graph",
+ "conditional",
+ "iframe",
+}
+
+
+def _is_mapping(v: Any) -> bool:
+ return isinstance(v, dict)
+
+
+def _is_sequence(v: Any) -> bool:
+ return isinstance(v, list)
+
+
+def _card_type(card: dict[str, Any]) -> str | None:
+ t = card.get("type")
+ if isinstance(t, str):
+ return t
+ return None
+
+
+def _validate_layout_depth(
+ *,
+ node: Any,
+ cur_depth: int,
+ max_depth: int,
+ node_path: str,
+ findings: list[Finding],
+) -> None:
+ if not _is_mapping(node):
+ return
+
+ ctype = _card_type(node)
+ if ctype in ALLOWED_LAYOUT:
+ cur_depth += 1
+ if cur_depth > max_depth:
+ findings.append(
+ Finding(
+ level="ERROR",
+ path=node_path,
+ message=f"Layout nesting depth {cur_depth} exceeds max {max_depth}.",
+ )
+ )
+
+ cards = node.get("cards")
+ if _is_sequence(cards):
+ for idx, child in enumerate(cards):
+ _validate_layout_depth(
+ node=child,
+ cur_depth=cur_depth,
+ max_depth=max_depth,
+ node_path=f"{node_path}.cards[{idx}]",
+ findings=findings,
+ )
+
+
+def _walk_cards(node: Any, node_path: str, findings: list[Finding]) -> None:
+ if _is_mapping(node):
+ ctype = _card_type(node)
+ if ctype in DISALLOWED_CARD_TYPES:
+ findings.append(
+ Finding(
+ level="ERROR",
+ path=node_path,
+ message=f"Disallowed card type: {ctype}",
+ )
+ )
+ if ctype and ctype not in ALLOWED_CARD_TYPES:
+ findings.append(
+ Finding(
+ level="WARN",
+ path=node_path,
+ message=f"Unknown/unlisted card type: {ctype}",
+ )
+ )
+
+ if ctype == "custom:button-card":
+ tmpl = node.get("template")
+ if tmpl is None:
+ findings.append(
+ Finding(
+ level="ERROR",
+ path=node_path,
+ message="custom:button-card must set template: (string or list).",
+ )
+ )
+ if "styles" in node:
+ findings.append(
+ Finding(
+ level="ERROR",
+ path=node_path,
+ message="Per-card styles: are not allowed; move styling into centralized templates.",
+ )
+ )
+ if "card_mod" in node:
+ findings.append(
+ Finding(
+ level="ERROR",
+ path=node_path,
+ message="Per-card card_mod: is not allowed on button-card instances; use shared snippets or templates.",
+ )
+ )
+
+ cards = node.get("cards")
+ if _is_sequence(cards):
+ for idx, child in enumerate(cards):
+ _walk_cards(child, f"{node_path}.cards[{idx}]", findings)
+
+ elif _is_sequence(node):
+ for idx, child in enumerate(node):
+ _walk_cards(child, f"{node_path}[{idx}]", findings)
+
+
+def _load_yaml(path: Path) -> Any:
+ txt = path.read_text(encoding="utf-8")
+ return yaml.load(txt, Loader=_Loader)
+
+
+def main(argv: list[str]) -> int:
+ ap = argparse.ArgumentParser()
+ ap.add_argument("view_yaml", type=Path, help="Path to a Lovelace view YAML file")
+ ap.add_argument("--max-layout-depth", type=int, default=2)
+ ap.add_argument("--strict", action="store_true", help="Treat WARN findings as errors (non-zero exit).")
+ args = ap.parse_args(argv)
+
+ if not args.view_yaml.exists():
+ print(f"ERROR: file not found: {args.view_yaml}", file=sys.stderr)
+ return 2
+
+ try:
+ doc = _load_yaml(args.view_yaml)
+ except Exception as e:
+ print(f"ERROR: failed to parse YAML: {e}", file=sys.stderr)
+ return 2
+
+ findings: list[Finding] = []
+
+ # The repo uses "one YAML file per view (single view dict)".
+ if not _is_mapping(doc):
+ findings.append(Finding(level="ERROR", path="$", message="View file must be a single YAML mapping (one view dict)."))
+ else:
+ # Support both classic views (`cards:`) and sections views (`type: sections`, `sections:`).
+ if "cards" in doc:
+ _validate_layout_depth(
+ node=doc,
+ cur_depth=0,
+ max_depth=args.max_layout_depth,
+ node_path="$",
+ findings=findings,
+ )
+ _walk_cards(doc.get("cards", []), "$.cards", findings)
+ elif doc.get("type") == "sections" and "sections" in doc:
+ sections = doc.get("sections")
+ if isinstance(sections, _Tagged):
+ findings.append(
+ Finding(
+ level="WARN",
+ path="$.sections",
+ message="sections is an !include/unknown tag; validator cannot inspect included sections content.",
+ )
+ )
+ elif _is_sequence(sections):
+ for sidx, section in enumerate(sections):
+ spath = f"$.sections[{sidx}]"
+ if not _is_mapping(section):
+ findings.append(Finding(level="ERROR", path=spath, message="Section must be a mapping."))
+ continue
+
+ _validate_layout_depth(
+ node=section,
+ cur_depth=0,
+ max_depth=args.max_layout_depth,
+ node_path=spath,
+ findings=findings,
+ )
+ _walk_cards(section.get("cards", []), f"{spath}.cards", findings)
+ else:
+ findings.append(
+ Finding(
+ level="ERROR",
+ path="$.sections",
+ message="sections must be a list (or an !include tag).",
+ )
+ )
+ else:
+ findings.append(
+ Finding(
+ level="ERROR",
+ path="$",
+ message="View dict must contain cards: or be type: sections with sections:.",
+ )
+ )
+
+ errors = [f for f in findings if f.level == "ERROR"]
+ warns = [f for f in findings if f.level == "WARN"]
+
+ for f in errors + warns:
+ print(f"{f.level}: {f.path}: {f.message}")
+
+ if errors:
+ return 1
+ if warns and args.strict:
+ return 1
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main(sys.argv[1:]))