Improve kiosk controls and distinguish dashboard notices from health issues

master
Carlo Costanzo 2 weeks ago committed by hass
parent b08a756c86
commit 649633c62c

@ -12,10 +12,11 @@
# Notes: Network trend panels live on Network so Overview stays focused on broad house status.
# Notes: Power & Solar shows realtime flow; accumulated history remains in Energy.
# Notes: Scheduled Proxmox, Docker host, and HVAC maintenance plus sustained WAN quality/reset warnings render amber; critical conditions remain red.
# Notes: Active Issue rows enumerate faults, scheduled maintenance, and reset urgency so drill-down is optional for initial triage.
# Notes: BearClaw scheduled-job warnings/errors roll up under Joanna and appear as an exceptions-only Active Issue row.
# Notes: Active Issue rows enumerate actionable faults and maintenance; optional notices stay in the blue banner.
# Notes: Scheduled-job warnings/errors appear individually, using the same rows as Joanna and the Systems count.
# Notes: Water and Systems surface normalized Rheem faults; telemetry-only loss renders amber.
# Notes: Back to Overview is the first control in the landing-view navigation strip.
# Notes: The landing strip omits the redundant Systems tile and keeps twelve category tiles.
# Notes: UPS health covers both Garage and Office NUT servers and drills into the shared UPS view.
# Notes: UPS alarms and telemetry loss roll up alongside utility outages; other submenu faults and Repairs gate Systems all-clear.
# Notes: Systems color follows the shared highest severity, with warnings amber and any critical issue red.
@ -45,6 +46,7 @@
"(max-width: 900px)":
grid-template-columns: repeat(2, minmax(0, 1fr))
cards:
- !include /config/dashboards/infrastructure/partials/informational_notices.yaml
- type: custom:button-card
template: bearstone_nav_back
name: Back to Overview
@ -55,18 +57,6 @@
action: navigate
navigation_path: /lovelace/home
- type: custom:button-card
template: bearstone_infra_status_kpi
entity: sensor.infra_dashboard_active_issue_count
name: Systems
icon: mdi:shield-check-outline
variables:
ok_state: '0'
healthy_text: ALL CLEAR
warning_text: '[[[ return `${entity.state} ACTIVE`; ]]]'
raw_suffix: ACTIVE
severity_attribute: severity
- type: custom:button-card
template: bearstone_infra_status_kpi
entity: binary_sensor.home_water_problem
@ -281,9 +271,9 @@
if (app === 'on') return 'MINI APP ERROR';
if (entity?.state === 'error') return 'JOB ERROR';
const security = states['sensor.joanna_security_updates'];
if (security?.attributes?.severity === 'critical') return 'CRITICAL REVIEW';
if (security?.attributes?.severity === 'critical') return 'URGENT CODEX';
if (security?.state === 'waiting_vendor_patch') return 'VENDOR PATCHES';
if (security?.state !== 'review') return security?.state === 'needs_attention' ? 'SECURITY REVIEW' : 'SECURITY COVERAGE';
if (security?.state !== 'review') return security?.state === 'needs_attention' ? 'NEEDS CODEX' : 'CODEX SCAN CHECK';
if (app !== 'off') return 'MINI APP UNKNOWN';
return ({ ok: 'HEALTHY', warning: 'JOB WARNING', error: 'JOB ERROR' })[entity?.state] || 'UNKNOWN';
]]]
@ -319,12 +309,12 @@
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Active Issue
name: Active Issues
- type: custom:button-card
template: bearstone_infra_alert_row
entity: sensor.joanna_security_updates
name: Security & Updates review
name: Security update work for Codex
icon: mdi:shield-search
variables:
alert_kind: state_not
@ -332,10 +322,10 @@
alert_severity: '[[[ return entity?.attributes?.severity === "critical" ? "critical" : "warning"; ]]]'
exception_detail: >
[[[ return entity?.state === 'needs_attention'
? `${entity?.attributes?.services_need_review ?? 0} SERVICES TO REVIEW · OPEN JOANNA FOR DETAILS`
: 'SCAN COVERAGE NEEDS REVIEW · OPEN JOANNA FOR DETAILS'; ]]]
? `${entity?.attributes?.services_need_review ?? 0} SERVICES NEED CODEX | OPEN JOANNA TO COPY PROMPT`
: 'SCAN COVERAGE NEEDS CODEX | OPEN JOANNA TO COPY PROMPT'; ]]]
state_display: >
[[[ return entity?.attributes?.severity === 'critical' ? 'CRITICAL REVIEW' : 'REVIEW'; ]]]
[[[ return entity?.attributes?.severity === 'critical' ? 'URGENT CODEX' : 'NEEDS CODEX'; ]]]
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/joanna
@ -563,30 +553,7 @@
action: navigate
navigation_path: /dashboard-infrastructure/kiosks
- type: custom:button-card
template: bearstone_infra_alert_row
entity: sensor.bearclaw_scheduled_job_health
name: >
[[[
const issues = entity?.attributes?.issues;
return Array.isArray(issues) && issues.length === 1
? issues[0].label : 'BearClaw scheduled jobs need attention';
]]]
icon: mdi:calendar-alert
variables:
alert_kind: state_not
ok_state: ok
alert_severity: '[[[ return entity?.state === "warning" ? "warning" : "critical"; ]]]'
exception_detail: >
[[[
const issues = entity?.attributes?.issues;
const detail = Array.isArray(issues) && issues.length === 1 ? issues[0].detail : null;
return String(detail || entity?.attributes?.summary || 'SCHEDULED JOB STATUS REQUIRES REVIEW');
]]]
state_display: '[[[ return entity?.state === "warning" ? "WARNING" : "ERROR"; ]]]'
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/joanna
- !include /config/dashboards/infrastructure/partials/scheduled_job_issues.yaml
- type: custom:button-card
template: bearstone_infra_alert_row
@ -927,7 +894,8 @@
icon: mdi:notebook-alert-outline
variables:
alert_kind: state_not
ok_state: ok
ok_states: [ok, indexing, recovering]
exception_detail: '[[[ return entity?.attributes?.detail || "OneNote index needs operator review"; ]]]'
alert_severity: '[[[ return entity?.state === "warning" ? "warning" : "critical"; ]]]'
tap_action:
action: navigate

@ -0,0 +1,28 @@
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Dashboard notices - optional appliance information
# A quiet full-width banner, separate from actionable health warnings.
# -------------------------------------------------------------------
# Notes: Native Markdown supplies HA's info alert and server-side expiry
# handling; button-card templates remain the status-card renderer.
# Notes: Reuses live-only telemetry; no extra sensor, history, or dispatch.
######################################################################
type: markdown
grid_options:
columns: full
view_layout:
grid-column: 1 / -1
text_only: true
show_empty: false
entity_id: sensor.bearclaw_status_telemetry
content: >-
{%- set health = state_attr('sensor.bearclaw_status_telemetry', 'scheduledJobsHealth') | default({}, true) -%}
{%- if is_state('sensor.bearclaw_status_telemetry', 'ok') -%}
{%- for notice in health.get('notices', []) if notice.get('severity') == 'info' and as_timestamp(notice.get('expiresAt'), 0) > as_timestamp(now()) -%}
<ha-alert alert-type="info"><b>{{ notice.get('label', '') | e }}</b><br>{{ notice.get('detail', '') | e }}</ha-alert>
{%- endfor -%}
{%- endif -%}

@ -115,23 +115,50 @@
ok_state: review
healthy_text: CLEAR
severity_attribute: severity
warning_text: NEEDS REVIEW
critical_text: CRITICAL REVIEW
warning_text: NEEDS CODEX
critical_text: URGENT CODEX
state_display: >
[[[ return entity?.attributes?.severity === 'critical' ? 'CRITICAL REVIEW' : entity?.state === 'needs_attention' ? 'NEEDS REVIEW' : entity?.state === 'review' ? 'CLEAR' : String(entity?.state ?? 'unknown').replaceAll('_', ' ').toUpperCase(); ]]]
[[[ return entity?.attributes?.severity === 'critical' ? 'URGENT CODEX' : entity?.state === 'needs_attention' ? 'NEEDS CODEX' : entity?.state === 'review' ? 'CLEAR' : String(entity?.state ?? 'unknown').replaceAll('_', ' ').toUpperCase(); ]]]
tap_action:
action: url
url_path: !secret bearclaw_security_admin_url
- type: custom:button-card
template: bearstone_infra_kpi
entity: sensor.joanna_security_updates
name: Services need review
icon: mdi:shield-alert-outline
name: Copy Codex prompt
icon: mdi:content-copy
state_display: >
[[[ return entity?.attributes?.codex_prompt ? `${entity?.attributes?.services_need_review ?? 0} SERVICES` : 'NOT NEEDED'; ]]]
tap_action:
action: javascript
javascript: >
[[[
const text = entity?.attributes?.codex_prompt;
const toast = (message) => this.dispatchEvent(new CustomEvent('hass-notification', {
bubbles: true, composed: true, detail: { message }
}));
if (['unknown', 'unavailable'].includes(entity?.state) || typeof text !== 'string' || !text.trim()) {
toast('No current Codex prompt is available.');
return;
}
const manual = () => window.prompt('Copy this prompt into Codex:', text);
const copied = () => toast('Copied. Paste into Codex to start the review.');
if (window.isSecureContext && navigator.clipboard?.writeText) {
navigator.clipboard.writeText(text).then(copied).catch(manual);
} else {
const area = document.createElement('textarea');
area.value = text;
document.body.appendChild(area);
area.focus(); area.select();
let ok = false;
try { ok = document.execCommand('copy'); } catch {}
area.remove();
if (ok) copied(); else manual();
}
]]]
hold_action:
action: url
url_path: !secret bearclaw_security_admin_url
state_display: >
[[[ return entity?.attributes?.services_need_review ?? '—'; ]]]
- type: custom:button-card
template: bearstone_infra_kpi
entity: sensor.joanna_security_updates
@ -300,27 +327,7 @@
return parts.join(' · ') || 'STATUS UNKNOWN';
]]]
- type: custom:button-card
template: bearstone_infra_alert_row
entity: sensor.bearclaw_scheduled_job_health
name: >
[[[
const issue = Array.isArray(entity?.attributes?.issues) ? entity.attributes.issues[0] : null;
return issue?.label || 'Scheduled job needs attention';
]]]
icon: mdi:alert-circle-outline
variables:
alert_kind: state_not
ok_state: ok
alert_severity: >
[[[ return entity?.state === 'warning' ? 'warning' : 'critical'; ]]]
exception_detail: >
[[[
const issue = Array.isArray(entity?.attributes?.issues) ? entity.attributes.issues[0] : null;
return String(issue?.detail || entity?.attributes?.summary || 'SCHEDULED JOB STATUS REQUIRES REVIEW');
]]]
state_display: >
[[[ return entity?.state === 'warning' ? 'WARNING' : 'ERROR'; ]]]
- !include /config/dashboards/infrastructure/partials/scheduled_job_issues.yaml
- type: custom:button-card
template: bearstone_infra_status_kpi
@ -343,12 +350,17 @@
entity: sensor.joanna_onenote_kb_health
name: OneNote account indexes
icon: mdi:notebook-multiple
show_label: true
label: '[[[ return entity?.attributes?.detail || ""; ]]]'
variables:
ok_state: ok
warning_state: warning
healthy_text: CURRENT
warning_text: SYNC STALE
critical_text: INDEX ERROR
show_detail: true
status_severity: >
[[[
const status = String(entity?.state ?? 'unknown');
if (['indexing', 'recovering'].includes(status)) return 'info';
if (status === 'ok') return 'healthy';
return status === 'warning' ? 'warning' : 'critical';
]]]
state_display: >
[[[
const docs = Number(entity?.attributes?.docs ?? 0).toLocaleString();

@ -4,9 +4,13 @@
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Infrastructure Partial - kiosks
# Related Issue: 2030
# Exception-first health, device telemetry, and live dashboard previews.
# -------------------------------------------------------------------
# Notes: Included by config/dashboards/infrastructure/views/05z_kiosks.yaml.
# Notes: Living Room TV is an on-demand kiosk, outside fleet health checks.
# Notes: Raven app status replaces the retired Samsung Haven browser preview.
# Notes: Kitchen Show power uses its charging sensor; battery telemetry is absent.
######################################################################
# -------------------------------------------------------------------
@ -131,63 +135,7 @@
[[[ return String(entity?.attributes?.status ?? entity?.state ?? 'unknown').replaceAll('_', ' ').toUpperCase(); ]]]
# -------------------------------------------------------------------
# Latest device screenshots
# -------------------------------------------------------------------
# Native picture cards provide live device screenshots; button-card cannot render camera images.
- type: grid
column_span: 4
columns: 4
square: false
cards:
- camera_view: auto
type: picture-glance
title: Bedroom Tablet
image: https://demo.home-assistant.io/stub_config/kitchen.png
entities:
- entity: binary_sensor.bedroom_kiosk_needs_attention
image_entity: image.alarm_panel_1_screenshot
tap_action:
action: navigate
navigation_path: /dashboard-bedroom/home
- camera_view: auto
type: picture-glance
title: Kitchen Show
image: https://demo.home-assistant.io/stub_config/kitchen.png
entities:
- entity: binary_sensor.kitchen_kiosk_needs_attention
- entity: sensor.kitchen_show_last_screenshot
camera_image: camera.kitchen_show_screenshot
tap_action:
action: navigate
navigation_path: /dashboard-kiosk-satellite-staging/home
- camera_view: auto
type: picture-glance
title: Upstairs Show
image: https://demo.home-assistant.io/stub_config/kitchen.png
entities:
- entity: binary_sensor.upstairs_kiosk_needs_attention
- entity: sensor.upstairs_show_last_screenshot
camera_image: camera.upstairs_show_screenshot
tap_action:
action: navigate
navigation_path: /dashboard-kiosk-satellite-staging/echo-show5
- camera_view: auto
type: picture-glance
title: Office Show
image: https://demo.home-assistant.io/stub_config/kitchen.png
entities:
- entity: binary_sensor.office_kiosk_needs_attention
- entity: sensor.office_show_last_screenshot
camera_image: camera.office_show_screenshot
tap_action:
action: navigate
navigation_path: /dashboard-kiosk-satellite-staging/echo-show5
# -------------------------------------------------------------------
# Living Room Nest - actual circular framebuffer preview
# Screens - device captures and native Roku Raven status
# -------------------------------------------------------------------
- type: grid
column_span: 4
@ -199,13 +147,62 @@
columns: full
layout_type: custom:grid-layout
layout:
grid-template-columns: minmax(0, 1fr) minmax(0, 2fr)
grid-template-columns: repeat(3, minmax(0, 1fr))
grid-gap: 12px
margin: 0
mediaquery:
"(max-width: 900px)":
"(max-width: 700px)":
grid-template-columns: minmax(0, 1fr)
"(max-width: 1200px)":
grid-template-columns: repeat(2, minmax(0, 1fr))
cards:
- camera_view: auto
type: picture-glance
title: Bedroom Tablet
image: https://demo.home-assistant.io/stub_config/kitchen.png
entities:
- entity: binary_sensor.bedroom_kiosk_needs_attention
image_entity: image.alarm_panel_1_screenshot
tap_action:
action: navigate
navigation_path: /dashboard-bedroom/home
- camera_view: auto
type: picture-glance
title: Kitchen Show
image: https://demo.home-assistant.io/stub_config/kitchen.png
entities:
- entity: binary_sensor.kitchen_kiosk_needs_attention
- entity: sensor.kitchen_show_last_screenshot
camera_image: camera.kitchen_show_screenshot
tap_action:
action: navigate
navigation_path: /dashboard-kiosk-satellite-staging/home
- camera_view: auto
type: picture-glance
title: Upstairs Show
image: https://demo.home-assistant.io/stub_config/kitchen.png
entities:
- entity: binary_sensor.upstairs_kiosk_needs_attention
- entity: sensor.upstairs_show_last_screenshot
camera_image: camera.upstairs_show_screenshot
tap_action:
action: navigate
navigation_path: /dashboard-kiosk-satellite-staging/echo-show5
- camera_view: auto
type: picture-glance
title: Office Show
image: https://demo.home-assistant.io/stub_config/kitchen.png
entities:
- entity: binary_sensor.office_kiosk_needs_attention
- entity: sensor.office_show_last_screenshot
camera_image: camera.office_show_screenshot
tap_action:
action: navigate
navigation_path: /dashboard-kiosk-satellite-staging/echo-show5
- type: custom:button-card
template: bearstone_infra_round_display
entity: image.living_room_nest_screen
@ -219,6 +216,368 @@
timestamp_entity: sensor.living_room_nest_last_screenshot
capture_entity: button.living_room_nest_capture_screen
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Raven - Living Room Roku
icon: mdi:television
- type: custom:button-card
template: bearstone_mobile_control
entity: sensor.living_room_ultra_active_app_id
name: Open Raven
show_entity_picture: true
entity_picture: /local/custom_ui/floorplan/images/branding/vCloudInfoCircle.png
triggers_update:
- media_player.samsung_q70_series_65
variables:
status_severity: >
[[[
const tv = states['media_player.samsung_q70_series_65']?.state;
if (tv === 'off') return 'active';
if (['unknown', 'unavailable', undefined].includes(entity?.state)) return 'warning';
return tv === 'on' && entity?.state === 'dev' ? 'healthy' : 'active';
]]]
state_display: >
[[[
const tv = states['media_player.samsung_q70_series_65']?.state;
if (tv === 'off') return 'TV OFF';
if (['unknown', 'unavailable', undefined].includes(entity?.state)) return 'ROKU UNAVAILABLE';
return entity?.state === 'dev' ? 'RAVEN RUNNING' : 'TAP TO OPEN';
]]]
tap_action:
action: perform-action
perform_action: rest_command.samsung_living_room_launch_haven
hold_action:
action: navigate
navigation_path: /dashboard-infrastructure/roku-remote
double_tap_action:
action: none
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.living_room_ultra_active_app
name: Current Roku app
icon: mdi:apps
triggers_update:
- sensor.living_room_ultra_active_app_id
state_display: >
[[[ return states['sensor.living_room_ultra_active_app_id']?.state === '562859' ? 'Roku Home' : entity?.state ?? 'Unknown'; ]]]
- type: custom:button-card
template: bearstone_infra_list_row
name: Roku Remote
icon: mdi:remote
show_state: false
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/roku-remote
# -------------------------------------------------------------------
# Device details - status and occasional controls
# -------------------------------------------------------------------
- type: grid
column_span: 4
columns: 1
square: false
cards:
- type: custom:layout-card
grid_options:
columns: full
layout_type: custom:grid-layout
layout:
grid-template-columns: repeat(3, minmax(0, 1fr))
grid-gap: 12px
margin: 0
mediaquery:
"(max-width: 700px)":
grid-template-columns: minmax(0, 1fr)
"(max-width: 1200px)":
grid-template-columns: repeat(2, minmax(0, 1fr))
cards:
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Bedroom Tablet
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.alarm_panel_1_battery
name: Battery
icon: mdi:battery
- type: custom:button-card
template: bearstone_infra_list_row
entity: binary_sensor.alarm_panel_1_plugged_in
name: Power
icon: mdi:power-plug
state_display: >
[[[ return entity?.state === 'on' ? 'PLUGGED IN' : 'ON BATTERY'; ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.alarm_panel_1_current_page
name: Dashboard
icon: mdi:web
state_display: >
[[[
const page = String(entity?.state ?? '');
return page.includes('/local/bedroom-kiosk.html') ? 'PAGE OK' : 'CHECK PAGE';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.alarm_panel_1_foreground_app
name: Foreground App
icon: mdi:application-outline
state_display: >
[[[ return entity?.state === 'de.ozerov.fully' ? 'FULLY KIOSK' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Kitchen Show
- type: custom:button-card
template: bearstone_infra_list_row
entity: binary_sensor.kitchen_show_charging
name: Power
icon: mdi:power-plug
state_display: >
[[[ return entity?.state === 'on' ? 'PLUGGED IN' : entity?.state === 'off' ? 'ON BATTERY' : 'UNKNOWN'; ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.kitchen_show_last_seen
name: Last Seen
icon: mdi:clock-check-outline
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.kitchen_show_current_page
name: Dashboard
icon: mdi:web
state_display: >
[[[
const page = String(entity?.state ?? '');
return page.includes('/dashboard-kiosk-satellite-staging/home') ? 'PAGE OK' : 'CHECK PAGE';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: update.kitchen_show_update
name: Kiosk Satellite
icon: mdi:update
state_display: >
[[[
const installed = entity?.attributes?.installed_version ?? 'unknown';
const latest = entity?.attributes?.latest_version ?? installed;
return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.kitchen_show_remote_admin
name: Remote Admin
icon: mdi:remote-desktop
state_display: AVAILABLE
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.kitchen_show_voice_satellite
name: Voice Assistant
icon: mdi:microphone
- type: custom:button-card
template: bearstone_infra_list_row
entity: select.kitchen_show_assistant
name: Assist Pipeline
icon: mdi:account-voice
- type: custom:button-card
template: bearstone_infra_list_row
entity: select.kitchen_show_wake_word
name: Wake Word
icon: mdi:ear-hearing
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Upstairs Show
- type: custom:button-card
template: bearstone_infra_list_row
entity: binary_sensor.upstairs_show_charging
name: Power
icon: mdi:power-plug
state_display: >
[[[ return states["binary_sensor.upstairs_show_charging"]?.state === "on" ? "PLUGGED IN" : "ON BATTERY"; ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.upstairs_show_last_seen
name: Last Seen
icon: mdi:clock-check-outline
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.upstairs_show_current_page
name: Dashboard
icon: mdi:web
state_display: >
[[[
const page = String(entity?.state ?? '');
return page.includes('/dashboard-kiosk-satellite-staging/echo-show5') ? 'PAGE OK' : 'CHECK PAGE';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.upstairs_show_foreground_app
name: Foreground App
icon: mdi:application-outline
state_display: >
[[[ return entity?.state === 'me.jxl.kiosk_satellite' ? 'KIOSK SATELLITE' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: light.upstairs_show_screen
name: Screen
icon: mdi:monitor
state_display: >
[[[ return String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: update.upstairs_show_update
name: Kiosk Satellite
icon: mdi:update
state_display: >
[[[
const installed = entity?.attributes?.installed_version ?? 'unknown';
const latest = entity?.attributes?.latest_version ?? installed;
return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.upstairs_show_remote_admin
name: Remote Admin
icon: mdi:remote-desktop
state_display: AVAILABLE
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.upstairs_show_voice_satellite
name: Voice Assistant
icon: mdi:microphone
- type: custom:button-card
template: bearstone_infra_list_row
entity: select.upstairs_show_assistant
name: Assist Pipeline
icon: mdi:account-voice
- type: custom:button-card
template: bearstone_infra_list_row
entity: select.upstairs_show_wake_word
name: Wake Word
icon: mdi:ear-hearing
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Office Show
- type: custom:button-card
template: bearstone_infra_list_row
entity: binary_sensor.office_show_charging
name: Power
icon: mdi:power-plug
state_display: >
[[[ return states["binary_sensor.office_show_charging"]?.state === "on" ? "PLUGGED IN" : "ON BATTERY"; ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.office_show_last_seen
name: Last Seen
icon: mdi:clock-check-outline
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.office_show_current_page
name: Dashboard
icon: mdi:web
state_display: >
[[[
const page = String(entity?.state ?? '');
return page.includes('/dashboard-kiosk-satellite-staging/echo-show5') ? 'PAGE OK' : 'CHECK PAGE';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.office_show_foreground_app
name: Foreground App
icon: mdi:application-outline
state_display: >
[[[ return entity?.state === 'me.jxl.kiosk_satellite' ? 'KIOSK SATELLITE' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: light.office_show_screen
name: Screen
icon: mdi:monitor
state_display: >
[[[ return String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: update.office_show_update
name: Kiosk Satellite
icon: mdi:update
state_display: >
[[[
const installed = entity?.attributes?.installed_version ?? 'unknown';
const latest = entity?.attributes?.latest_version ?? installed;
return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.office_show_remote_admin
name: Remote Admin
icon: mdi:remote-desktop
state_display: AVAILABLE
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.office_show_voice_satellite
name: Voice Assistant
icon: mdi:microphone
- type: custom:button-card
template: bearstone_infra_list_row
entity: select.office_show_assistant
name: Assist Pipeline
icon: mdi:account-voice
- type: custom:button-card
template: bearstone_infra_list_row
entity: select.office_show_wake_word
name: Wake Word
icon: mdi:ear-hearing
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
@ -289,291 +648,70 @@
target:
entity_id: button.living_room_nest_restart_display
# -------------------------------------------------------------------
# Device telemetry
# -------------------------------------------------------------------
- type: grid
column_span: 4
columns: 4
square: false
cards:
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Bedroom Tablet
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.alarm_panel_1_battery
name: Battery
icon: mdi:battery
- type: custom:button-card
template: bearstone_infra_list_row
entity: binary_sensor.alarm_panel_1_plugged_in
name: Power
icon: mdi:power-plug
state_display: >
[[[ return entity?.state === 'on' ? 'PLUGGED IN' : 'ON BATTERY'; ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.alarm_panel_1_current_page
name: Dashboard
icon: mdi:web
state_display: >
[[[
const page = String(entity?.state ?? '');
return page.includes('/local/bedroom-kiosk.html') ? 'PAGE OK' : 'CHECK PAGE';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.alarm_panel_1_foreground_app
name: Foreground App
icon: mdi:application-outline
state_display: >
[[[ return entity?.state === 'de.ozerov.fully' ? 'FULLY KIOSK' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Kitchen Show
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.kitchen_show_battery
name: Battery
icon: mdi:battery
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.kitchen_show_last_seen
name: Last Seen
icon: mdi:clock-check-outline
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.kitchen_show_current_page
name: Dashboard
icon: mdi:web
state_display: >
[[[
const page = String(entity?.state ?? '');
return page.includes('/dashboard-kiosk-satellite-staging/home') ? 'PAGE OK' : 'CHECK PAGE';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: update.kitchen_show_update
name: Kiosk Satellite
icon: mdi:update
state_display: >
[[[
const installed = entity?.attributes?.installed_version ?? 'unknown';
const latest = entity?.attributes?.latest_version ?? installed;
return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.kitchen_show_remote_admin
name: Remote Admin
icon: mdi:remote-desktop
state_display: AVAILABLE
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.kitchen_show_voice_satellite
name: Voice Assistant
icon: mdi:microphone
- type: custom:button-card
template: bearstone_infra_list_row
entity: select.kitchen_show_assistant
name: Assist Pipeline
icon: mdi:account-voice
- type: custom:button-card
template: bearstone_infra_list_row
entity: select.kitchen_show_wake_word
name: Wake Word
icon: mdi:ear-hearing
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Upstairs Show
- type: custom:button-card
template: bearstone_infra_list_row
entity: binary_sensor.upstairs_show_charging
name: Power
icon: mdi:power-plug
state_display: >
[[[ return states["binary_sensor.upstairs_show_charging"]?.state === "on" ? "PLUGGED IN" : "ON BATTERY"; ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.upstairs_show_last_seen
name: Last Seen
icon: mdi:clock-check-outline
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.upstairs_show_current_page
name: Dashboard
icon: mdi:web
state_display: >
[[[
const page = String(entity?.state ?? '');
return page.includes('/dashboard-kiosk-satellite-staging/echo-show5') ? 'PAGE OK' : 'CHECK PAGE';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.upstairs_show_foreground_app
name: Foreground App
icon: mdi:application-outline
state_display: >
[[[ return entity?.state === 'me.jxl.kiosk_satellite' ? 'KIOSK SATELLITE' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: light.upstairs_show_screen
name: Screen
icon: mdi:monitor
state_display: >
[[[ return String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: update.upstairs_show_update
name: Kiosk Satellite
icon: mdi:update
state_display: >
[[[
const installed = entity?.attributes?.installed_version ?? 'unknown';
const latest = entity?.attributes?.latest_version ?? installed;
return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.upstairs_show_remote_admin
name: Remote Admin
icon: mdi:remote-desktop
state_display: AVAILABLE
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.upstairs_show_voice_satellite
name: Voice Assistant
icon: mdi:microphone
- type: custom:button-card
template: bearstone_infra_list_row
entity: select.upstairs_show_assistant
name: Assist Pipeline
icon: mdi:account-voice
- type: custom:button-card
template: bearstone_infra_list_row
entity: select.upstairs_show_wake_word
name: Wake Word
icon: mdi:ear-hearing
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Office Show
- type: custom:button-card
template: bearstone_infra_list_row
entity: binary_sensor.office_show_charging
name: Power
icon: mdi:power-plug
state_display: >
[[[ return states["binary_sensor.office_show_charging"]?.state === "on" ? "PLUGGED IN" : "ON BATTERY"; ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.office_show_last_seen
name: Last Seen
icon: mdi:clock-check-outline
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.office_show_current_page
name: Dashboard
icon: mdi:web
state_display: >
[[[
const page = String(entity?.state ?? '');
return page.includes('/dashboard-kiosk-satellite-staging/echo-show5') ? 'PAGE OK' : 'CHECK PAGE';
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.office_show_foreground_app
name: Foreground App
icon: mdi:application-outline
state_display: >
[[[ return entity?.state === 'me.jxl.kiosk_satellite' ? 'KIOSK SATELLITE' : String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: light.office_show_screen
name: Screen
icon: mdi:monitor
state_display: >
[[[ return String(entity?.state ?? 'UNKNOWN').toUpperCase(); ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: update.office_show_update
name: Kiosk Satellite
icon: mdi:update
state_display: >
[[[
const installed = entity?.attributes?.installed_version ?? 'unknown';
const latest = entity?.attributes?.latest_version ?? installed;
return entity?.state === 'on' ? `UPDATE ${latest}` : `CURRENT ${installed}`;
]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.office_show_remote_admin
name: Remote Admin
icon: mdi:remote-desktop
state_display: AVAILABLE
- type: custom:button-card
template: bearstone_infra_list_row
entity: sensor.office_show_voice_satellite
name: Voice Assistant
icon: mdi:microphone
- type: custom:button-card
template: bearstone_infra_list_row
entity: select.office_show_assistant
name: Assist Pipeline
icon: mdi:account-voice
- type: custom:button-card
template: bearstone_infra_list_row
entity: select.office_show_wake_word
name: Wake Word
icon: mdi:ear-hearing
- type: custom:vertical-stack-in-card
card_mod:
style: !include /config/dashboards/infrastructure/card_mod/infra_panel.yaml
cards:
- type: custom:button-card
template: bearstone_infra_panel_header
name: Raven - Living Room TV
icon: mdi:television
- type: custom:button-card
template: bearstone_infra_list_row
entity: media_player.samsung_q70_series_65
name: TV power
icon: mdi:power
state_display: >
[[[ return entity?.state === 'off' ? 'OFF (NORMAL)' : String(entity?.state ?? 'unknown').toUpperCase(); ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: timer.roku_haven_manual_exit
name: Manual-exit pause
icon: mdi:pause-circle-outline
state_display: >
[[[ return entity?.state === 'active' ? 'PAUSED AFTER EXIT' : entity?.state === 'idle' ? 'READY' : 'UNKNOWN'; ]]]
- type: custom:button-card
template: bearstone_infra_list_row
entity: input_boolean.haven
name: Automatic Raven launch
icon: mdi:play-circle-outline
state_display: >
[[[ return entity?.state === 'on' ? 'ENABLED' : entity?.state === 'off' ? 'PAUSED UNTIL 3 AM' : 'UNKNOWN'; ]]]
tap_action:
action: toggle
- type: custom:button-card
template: bearstone_infra_list_row
entity: automation.living_room_tv_haven_on_roku_screensaver
name: Last automatic launch
icon: mdi:history
state_display: >
[[[
const last = entity?.attributes?.last_triggered;
if (!last) return 'NOT YET';
const date = new Date(last);
return Number.isNaN(date.getTime()) ? 'UNKNOWN' : date.toLocaleString(undefined, {month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit'});
]]]
- type: custom:button-card
template: bearstone_infra_list_row
name: Open Raven
icon: mdi:view-dashboard-outline
show_state: false
tap_action:
action: perform-action
perform_action: rest_command.samsung_living_room_launch_haven
- type: custom:button-card
template: bearstone_infra_list_row
name: Roku Remote
icon: mdi:remote
show_state: false
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/roku-remote
- type: custom:button-card
template: bearstone_infra_list_row
name: TV Remote
icon: mdi:remote-tv
show_state: false
tap_action:
action: navigate
navigation_path: /dashboard-infrastructure/tv-remote

@ -0,0 +1,44 @@
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Scheduled Job Issues - shared exceptions for Systems and Joanna
# Notes: Existing alert-row styling preserves each job's own severity.
# Notes: Auto-entities is needed to render a changing number of job rows.
######################################################################
type: custom:auto-entities
show_empty: false
card:
type: grid
columns: 1
square: false
card_param: cards
filter:
template: >-
{% from 'infrastructure_health.jinja' import scheduled_job_issues %}
{% set issues = (scheduled_job_issues | as_function)() %}
{% set ns = namespace(cards=[]) %}
{% for issue in issues %}
{% set next_run = as_datetime(issue.get('nextRunAt'), none) %}
{% set detail = issue.get('detail') or 'Open BearClaw Admin > Activity > Scheduled Jobs to review this warning.' %}
{% if next_run is not none %}
{% set detail = detail ~ ' Next check: ' ~ as_local(next_run).strftime('%b %d, %I:%M %p') ~ '.' %}
{% endif %}
{% set warning = issue.get('severity') == 'warning' %}
{% set ns.cards = ns.cards + [{
'type': 'custom:button-card', 'template': 'bearstone_infra_alert_row',
'entity': 'sensor.bearclaw_scheduled_job_health',
'name': issue.get('label') or 'Scheduled job needs attention',
'icon': 'mdi:calendar-alert',
'variables': {
'alert_kind': 'state_not', 'ok_state': 'ok',
'alert_severity': 'warning' if warning else 'critical',
'exception_detail': detail
},
'state_display': 'WARNING' if warning else 'ERROR',
'tap_action': {'action': 'more-info'}
}] %}
{% endfor %}
{{ ns.cards | to_json }}

@ -5,6 +5,7 @@
# -------------------------------------------------------------------
# Infrastructure Templates - custom:button-card
# Related Issue: 1560
# Related Issue: 2029
# Shared `button_card_templates` used across Infrastructure and Overview views.
# -------------------------------------------------------------------
# Notes: Keep templates generic; view-specific tuning stays in the view partials.
@ -749,7 +750,12 @@ bearstone_infra_container_row:
return (hasRestart && isMonitored) ? 'block' : 'none';
]]]
# Seasonal paint stays inside the existing header; dimensions and content are unchanged.
bearstone_infra_panel_header:
variables:
seasonal: '[[[ return window.BearStoneKioskAmbient?.seasonal?.(states) || {mode: "standard", wash: "none", emblem: ""}; ]]]'
triggers_update:
- sensor.holiday_lighting_mode
show_icon: false
show_state: false
show_label: false
@ -758,6 +764,7 @@ bearstone_infra_panel_header:
styles:
card:
- background: transparent
- background-image: '[[[ return variables.seasonal.wash; ]]]'
- border: none
- box-shadow: none
- padding: 6px 2px 10px 2px
@ -770,6 +777,8 @@ bearstone_infra_panel_header:
bearstone_infra_panel_header_icon:
template: bearstone_infra_panel_header
show_entity_picture: '[[[ return Boolean(variables.seasonal.emblem); ]]]'
entity_picture: '[[[ return variables.seasonal.emblem || ""; ]]]'
show_icon: true
show_name: true
icon: mdi:view-dashboard-outline
@ -917,7 +926,7 @@ bearstone_infra_alert_row:
else if (kind === 'binary_off') show = (stateStr === 'off');
else if (kind === 'state_not') {
const okState = String(variables?.ok_state ?? '').toLowerCase();
show = stateStr.toLowerCase() !== okState;
show = !(Array.isArray(variables?.ok_states) ? variables.ok_states.map(value => String(value).toLowerCase()) : [okState]).includes(stateStr.toLowerCase());
}
else if (kind === 'state_is') {
const alertState = String(variables?.alert_state ?? '').toLowerCase();
@ -1126,10 +1135,21 @@ bearstone_infra_status_kpi:
return 'ATTENTION';
]]]
styles:
grid:
- grid-template-areas: '[[[ return variables?.show_detail ? `"i n" "s s" "l l"` : `"i n" "s s"`; ]]]'
- grid-template-rows: '[[[ return variables?.show_detail ? "20px min-content min-content" : "20px min-content"; ]]]'
label:
- grid-area: l
- white-space: normal
- text-align: center
- font-size: 12px
- line-height: 16px
- opacity: 0.8
card:
- border-color: >
[[[
const severity = String(variables?.status_severity ?? '');
if (severity === 'info') return 'rgba(30,136,229,0.40)';
if (severity === 'healthy') return 'rgba(67,160,71,0.45)';
if (severity === 'critical') return 'rgba(229,57,53,0.40)';
return 'rgba(245,124,0,0.45)';
@ -1137,6 +1157,7 @@ bearstone_infra_status_kpi:
- background: >
[[[
const severity = String(variables?.status_severity ?? '');
if (severity === 'info') return 'rgba(227,242,253,0.85)';
if (severity === 'healthy') return 'rgba(232,245,233,0.85)';
if (severity === 'critical') return 'rgba(255,235,238,0.85)';
return 'rgba(255,243,224,0.85)';
@ -1145,6 +1166,7 @@ bearstone_infra_status_kpi:
- color: >
[[[
const severity = String(variables?.status_severity ?? '');
if (severity === 'info') return 'rgba(21,101,192,1)';
if (severity === 'healthy') return 'rgba(46,125,50,1)';
if (severity === 'critical') return 'rgba(198,40,40,1)';
return 'rgba(230,81,0,1)';
@ -1153,6 +1175,7 @@ bearstone_infra_status_kpi:
- color: >
[[[
const severity = String(variables?.status_severity ?? '');
if (severity === 'info') return 'rgba(21,101,192,1)';
if (severity === 'healthy') return 'rgba(46,125,50,1)';
if (severity === 'critical') return 'rgba(198,40,40,1)';
return 'rgba(230,81,0,1)';

@ -0,0 +1,268 @@
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Infrastructure Subview - Living Room TV Remote
# Related Issue: 2030
# Large navigation, input/settings menus, and the Raven app launcher.
# -------------------------------------------------------------------
# Notes: Open from Living Room TV on the Kiosks page.
# Notes: Each tap sends one key; Back and Exit control the TV menu.
# Notes: TV Home exposes Sources; Settings opens TV sound and Anynet+ options.
# Notes: KEY_SOURCE did not open this TV's input menu; KEY_HOME is verified.
# Notes: Reuses the 112px mobile controls without automatic key presses.
# Notes: Raven launches the native Roku app through the display bridge.
######################################################################
path: tv-remote
title: TV Remote
icon: mdi:remote-tv
subview: true
type: sections
max_columns: 4
badges: []
sections:
- type: grid
column_span: 4
columns: 1
square: false
cards:
- type: custom:layout-card
grid_options:
columns: full
layout_type: custom:grid-layout
layout:
grid-template-columns: repeat(6, minmax(0, 1fr))
grid-auto-flow: row
grid-auto-rows: min-content
align-items: stretch
grid-gap: 10px
margin: 0
cards:
- type: custom:button-card
template: bearstone_mobile_header
name: Living Room TV
icon: mdi:television
view_layout:
grid-column: 1 / -1
- type: custom:button-card
template: bearstone_mobile_control
entity: media_player.living_room_ultra
name: Raven
icon: mdi:view-dashboard-outline
show_state: false
variables:
status_severity: healthy
view_layout:
grid-column: 1 / -1
tap_action:
action: perform-action
perform_action: rest_command.samsung_living_room_launch_haven
hold_action:
action: none
double_tap_action:
action: none
# Samsung menus stay accessible without a physical TV remote.
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.samsung_q70_series_65
name: TV Home
icon: mdi:home-outline
show_state: false
variables:
status_severity: active
view_layout:
grid-column: span 3
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.samsung_q70_series_65
data:
command: KEY_HOME
hold_action:
action: none
double_tap_action:
action: none
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.samsung_q70_series_65
name: Settings
icon: mdi:cog
show_state: false
variables:
status_severity: active
view_layout:
grid-column: span 3
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.samsung_q70_series_65
data:
command: KEY_MENU
hold_action:
action: none
double_tap_action:
action: none
# Full-width Up and Down keep every touch target large without spacers.
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.samsung_q70_series_65
name: Up
icon: mdi:arrow-up-bold
show_state: false
variables:
status_severity: active
view_layout:
grid-column: 1 / -1
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.samsung_q70_series_65
data:
command: KEY_UP
hold_action:
action: none
double_tap_action:
action: none
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.samsung_q70_series_65
name: Left
icon: mdi:arrow-left-bold
show_state: false
variables:
status_severity: active
view_layout:
grid-column: span 2
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.samsung_q70_series_65
data:
command: KEY_LEFT
hold_action:
action: none
double_tap_action:
action: none
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.samsung_q70_series_65
name: OK
icon: mdi:check-bold
show_state: false
variables:
status_severity: healthy
view_layout:
grid-column: span 2
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.samsung_q70_series_65
data:
command: KEY_ENTER
hold_action:
action: none
double_tap_action:
action: none
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.samsung_q70_series_65
name: Right
icon: mdi:arrow-right-bold
show_state: false
variables:
status_severity: active
view_layout:
grid-column: span 2
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.samsung_q70_series_65
data:
command: KEY_RIGHT
hold_action:
action: none
double_tap_action:
action: none
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.samsung_q70_series_65
name: Down
icon: mdi:arrow-down-bold
show_state: false
variables:
status_severity: active
view_layout:
grid-column: 1 / -1
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.samsung_q70_series_65
data:
command: KEY_DOWN
hold_action:
action: none
double_tap_action:
action: none
# These buttons navigate the TV, rather than the Home Assistant screen.
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.samsung_q70_series_65
name: Back
icon: mdi:undo
show_state: false
variables:
status_severity: active
view_layout:
grid-column: span 3
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.samsung_q70_series_65
data:
command: KEY_RETURN
hold_action:
action: none
double_tap_action:
action: none
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.samsung_q70_series_65
name: Exit
icon: mdi:close-circle-outline
show_state: false
variables:
status_severity: active
view_layout:
grid-column: span 3
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.samsung_q70_series_65
data:
command: KEY_EXIT
hold_action:
action: none
double_tap_action:
action: none
cards: []

@ -0,0 +1,266 @@
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Infrastructure Subview - Living Room Roku Remote
# Related Issue: 2030
# Native Roku navigation, playback, and the Raven app launcher.
# -------------------------------------------------------------------
# Notes: Open from the Raven card or device panel on Systems > Kiosks.
# Notes: Each tap sends one Roku command; Back/Home exit Raven natively.
# Notes: TV menus remain on the separate Samsung TV Remote subview.
# Notes: Reuses the existing large mobile controls and launch action.
######################################################################
path: roku-remote
title: Roku Remote
icon: mdi:remote-tv
subview: true
type: sections
max_columns: 4
badges: []
sections:
- type: grid
column_span: 4
columns: 1
square: false
cards:
- type: custom:layout-card
grid_options:
columns: full
layout_type: custom:grid-layout
layout:
grid-template-columns: repeat(6, minmax(0, 1fr))
grid-auto-flow: row
grid-auto-rows: min-content
align-items: stretch
grid-gap: 10px
margin: 0
cards:
- type: custom:button-card
template: bearstone_mobile_header
name: Living Room Roku
icon: mdi:television
view_layout:
grid-column: 1 / -1
- type: custom:button-card
template: bearstone_mobile_control
entity: media_player.living_room_ultra
name: Raven
icon: mdi:view-dashboard-outline
show_state: false
variables:
status_severity: healthy
view_layout:
grid-column: 1 / -1
tap_action:
action: perform-action
perform_action: rest_command.samsung_living_room_launch_haven
hold_action:
action: none
double_tap_action:
action: none
# Roku controls never send Samsung input or menu commands.
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.living_room_ultra
name: Roku Home
icon: mdi:home-outline
show_state: false
variables:
status_severity: active
view_layout:
grid-column: span 3
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.living_room_ultra
data:
command: home
hold_action:
action: none
double_tap_action:
action: none
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.living_room_ultra
name: Options
icon: mdi:dots-horizontal
show_state: false
variables:
status_severity: active
view_layout:
grid-column: span 3
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.living_room_ultra
data:
command: info
hold_action:
action: none
double_tap_action:
action: none
# Full-width Up and Down keep every touch target large without spacers.
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.living_room_ultra
name: Up
icon: mdi:arrow-up-bold
show_state: false
variables:
status_severity: active
view_layout:
grid-column: 1 / -1
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.living_room_ultra
data:
command: up
hold_action:
action: none
double_tap_action:
action: none
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.living_room_ultra
name: Left
icon: mdi:arrow-left-bold
show_state: false
variables:
status_severity: active
view_layout:
grid-column: span 2
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.living_room_ultra
data:
command: left
hold_action:
action: none
double_tap_action:
action: none
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.living_room_ultra
name: OK
icon: mdi:check-bold
show_state: false
variables:
status_severity: healthy
view_layout:
grid-column: span 2
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.living_room_ultra
data:
command: select
hold_action:
action: none
double_tap_action:
action: none
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.living_room_ultra
name: Right
icon: mdi:arrow-right-bold
show_state: false
variables:
status_severity: active
view_layout:
grid-column: span 2
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.living_room_ultra
data:
command: right
hold_action:
action: none
double_tap_action:
action: none
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.living_room_ultra
name: Down
icon: mdi:arrow-down-bold
show_state: false
variables:
status_severity: active
view_layout:
grid-column: 1 / -1
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.living_room_ultra
data:
command: down
hold_action:
action: none
double_tap_action:
action: none
# Back exits Raven; playback controls act on the current Roku app.
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.living_room_ultra
name: Back
icon: mdi:undo
show_state: false
variables:
status_severity: active
view_layout:
grid-column: span 3
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.living_room_ultra
data:
command: back
hold_action:
action: none
double_tap_action:
action: none
- type: custom:button-card
template: bearstone_mobile_control
entity: remote.living_room_ultra
name: Play / Pause
icon: mdi:play-pause
show_state: false
variables:
status_severity: active
view_layout:
grid-column: span 3
tap_action:
action: perform-action
perform_action: remote.send_command
target:
entity_id: remote.living_room_ultra
data:
command: play
hold_action:
action: none
double_tap_action:
action: none
cards: []

@ -4,12 +4,14 @@
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Kiosk Satellite Templates - Shared alerts and compact Echo Show 5
# Related Issue: 2029
# One household alert renderer with opt-in small-screen presentation.
# -------------------------------------------------------------------
# Notes: Kitchen keeps its existing dimensions and interactive details.
# Notes: Echo Show 5 uses compact interactive alerts and minute-updated time.
# Notes: Shared phone notifications join the ambient rotation with sent times and ten-minute expiry.
# Notes: Shared phone notifications retain the source feed's expiry and sent times.
# Notes: Direct touch release handles kiosk taps before button-card suppresses synthetic clicks.
# Notes: Shared holiday accents decorate ambient panels only; alert colors retain their meaning.
######################################################################
kiosk_ambient_alerts:
@ -25,75 +27,18 @@ kiosk_ambient_alerts:
action: none
double_tap_action:
action: none
triggers_update:
- binary_sensor.garage_phyn_battery_alert
- binary_sensor.garage_phyn_freeze_warning_alert
- binary_sensor.garage_phyn_high_pressure_alert
- binary_sensor.garage_phyn_leak_alert
- binary_sensor.garage_phyn_offline_leak_shutoff_alert
- binary_sensor.garage_phyn_pinhole_leak_alert
- binary_sensor.garage_phyn_recurring_flow_alert
- binary_sensor.garage_phyn_temperature_alert
- valve.phyn_shutoff_valve
- sensor.phyn_current_water_pressure
- sensor.phyn_average_water_flow_rate
- sensor.nws_alerts_alerts
- group.garage_doors
- group.entry_points
- group.locks
- binary_sensor.front_door_packages_present
- sensor.rheem_wh_available_hot_water
- sensor.rheem_wh_alert_count
- sensor.rheem_wh_diagnostic_status
- binary_sensor.rheem_wh_problem
- binary_sensor.rheem_wh_active_alert
- binary_sensor.rheem_wh_telemetry_stale
- binary_sensor.rheem_wh_running
- sensor.rheem_wh_tank_health
- sensor.rheem_wh_compressor_health
- water_heater.rheem_wh
- light.sink
- input_boolean.garbage_cans_out
- input_datetime.water_delivery_date
- sensor.water_softener_salt_days_since_last_add
- sensor.water_softener_salt_average_days_between_refills
- sensor.spaceship_battery_level
- switch.spaceship_charge
- input_datetime.tesla_model_y_last_garage_close
- input_datetime.tesla_model_y_plug_in_snooze_until
- sensor.next_booked_cruise_countdown
- sensor.bearclaw_status_telemetry
- sensor.kiosk_recent_personal_records
- sensor.kiosk_recent_phone_notifications
- person.carlo
- person.stacey
- person.justin
- person.paige
- sensor.carlo_location_display
- sensor.stacey_location_display
- sensor.justin_location_display
- sensor.paige_location_display
- binary_sensor.kiosk_rain_radar_weather_event
- binary_sensor.kiosk_rain_radar_active
- timer.kitchen_kiosk_radar_display
- input_text.kitchen_kiosk_detail_view
- timer.kitchen_kiosk_detail_display
- sensor.kiosk_active_camera_view
- camera.frontdoorbell
# Model dependencies may grow; a bounded refresh keeps every Show current.
update_timer: 5s
triggers_update: update_timer
custom_fields:
ambient: >
[[[
const state = (entityId) => states[entityId]?.state;
const valid = (value) => !['unknown', 'unavailable', 'none', '', undefined].includes(value);
const pretty = (value) => String(value || 'Unknown').replaceAll('_', ' ');
const dateKey = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
const items = [];
const now = new Date();
const model = window.BearStoneKioskAmbient;
if (!model) return html`<div>Loading household notifications...</div>`;
const feed = model.build(states, now);
const seasonal = model.seasonal ? model.seasonal(states) : {mode: 'standard'};
const {items, state, valid, pretty, dateKey, cleanAlertText, alertSection, limitAlertText, uniqueWeatherAlerts, phynAlerts, rheemProblem} = feed;
const detailValue = valid(state('input_text.kitchen_kiosk_detail_view'))
? state('input_text.kitchen_kiosk_detail_view')
: 'none';
@ -111,434 +56,6 @@ kiosk_ambient_alerts:
});
};
const radarWeatherEvent = states['binary_sensor.kiosk_rain_radar_weather_event'];
const radarDisplayTimer = states['timer.kitchen_kiosk_radar_display'];
const radarReplayReady =
radarWeatherEvent?.state === 'on' &&
state('binary_sensor.kiosk_rain_radar_active') !== 'on' &&
radarDisplayTimer?.state === 'idle' &&
new Date(radarDisplayTimer.last_changed).getTime() >
new Date(radarWeatherEvent.last_changed).getTime();
if (radarReplayReady) {
items.push({
kicker: 'WEATHER RADAR',
title: 'Storms Approaching',
detail: 'Tap for radar',
icon: 'mdi:radar',
accent: '#0288d1',
action: {
service: 'kitchen_kiosk_show_event',
data: {event_type: 'radar'}
}
});
}
const phynAlerts = [
'binary_sensor.garage_phyn_battery_alert',
'binary_sensor.garage_phyn_freeze_warning_alert',
'binary_sensor.garage_phyn_high_pressure_alert',
'binary_sensor.garage_phyn_leak_alert',
'binary_sensor.garage_phyn_offline_leak_shutoff_alert',
'binary_sensor.garage_phyn_pinhole_leak_alert',
'binary_sensor.garage_phyn_recurring_flow_alert',
'binary_sensor.garage_phyn_temperature_alert'
].filter((entityId) => state(entityId) === 'on');
const phynValveState = state('valve.phyn_shutoff_valve');
if (phynAlerts.length > 0 || (valid(phynValveState) && phynValveState !== 'open')) {
const alertNames = phynAlerts.map((entityId) =>
states[entityId]?.attributes?.friendly_name || pretty(entityId.split('.')[1])
);
items.push({
kicker: 'WATER SYSTEM',
title: phynValveState === 'closed' ? 'Main water is off' : 'Phyn needs attention',
detail: `${alertNames.join(' · ') || `Valve is ${pretty(phynValveState)}`} · Tap for water details`,
icon: 'mdi:water-alert-outline',
accent: '#c62828',
action: {
service: 'kitchen_kiosk_show_detail',
data: {detail_type: 'phyn'}
}
});
}
const weatherAlertEntity = states['sensor.nws_alerts_alerts'];
const weatherAlertCount = Number.parseInt(weatherAlertEntity?.state, 10);
const weatherAlerts = Array.isArray(weatherAlertEntity?.attributes?.Alerts)
? weatherAlertEntity.attributes.Alerts
: [];
const cleanAlertText = (value) => String(value || '').replace(/\s+/g, ' ').trim();
const alertSection = (description, label) => {
const pattern = new RegExp(
`\\*\\s*${label}\\.{3}\\s*([\\s\\S]*?)(?=\\n\\s*\\n\\s*\\*\\s*[A-Z ]+\\.{3}|$)`,
'i'
);
return cleanAlertText(String(description || '').match(pattern)?.[1]);
};
const limitAlertText = (value, maxLength = 240) => {
const text = cleanAlertText(value);
if (text.length <= maxLength) return text;
return `${text.slice(0, maxLength - 3).replace(/\s+\S*$/, '')}...`;
};
const severityOrder = { Extreme: 0, Severe: 1, Moderate: 2, Minor: 3, Unknown: 4 };
const severityColors = {
Extreme: '#b71c1c',
Severe: '#c62828',
Moderate: '#d84315',
Minor: '#f9a825',
Unknown: '#546e7a'
};
const uniqueWeatherAlerts = weatherAlerts
.filter((alert, index, alerts) => {
const alertKey = alert.ID || alert.URL || `${alert.Event}|${alert.Expires}`;
return alerts.findIndex((candidate) =>
(candidate.ID || candidate.URL || `${candidate.Event}|${candidate.Expires}`) === alertKey
) === index;
})
.sort((left, right) =>
(severityOrder[left.Severity] ?? severityOrder.Unknown)
- (severityOrder[right.Severity] ?? severityOrder.Unknown)
);
if (uniqueWeatherAlerts.length > 0) {
uniqueWeatherAlerts.forEach((alert, index) => {
const severity = severityColors[alert.Severity] ? alert.Severity : 'Unknown';
const detailParts = [
alertSection(alert.Description, 'WHAT'),
alertSection(alert.Description, 'WHERE'),
alertSection(alert.Description, 'WHEN')
].filter(Boolean);
const detail = detailParts.length > 0
? detailParts.join(' · ')
: alert.Headline || alert.Description || 'Active National Weather Service alert';
const position = uniqueWeatherAlerts.length > 1
? ` ${index + 1}/${uniqueWeatherAlerts.length}`
: '';
items.push({
kicker: `WEATHER ALERT${position} · ${severity.toUpperCase()}`,
title: alert.Event || `${weatherAlertCount} active alerts`,
detail: limitAlertText(detail),
icon: 'mdi:weather-lightning-rainy',
accent: severityColors[severity],
action: {
service: 'kitchen_kiosk_show_detail',
data: {
detail_type: 'weather',
detail_subject: String(alert.ID || index)
}
}
});
});
} else if (Number.isFinite(weatherAlertCount) && weatherAlertCount > 0) {
items.push({
kicker: 'WEATHER ALERT',
title: `${weatherAlertCount} active ${weatherAlertCount === 1 ? 'alert' : 'alerts'}`,
detail: 'Alert details are temporarily unavailable',
icon: 'mdi:weather-lightning-rainy',
accent: severityColors.Unknown,
action: {
service: 'kitchen_kiosk_show_detail',
data: {detail_type: 'weather', detail_subject: '0'}
}
});
}
if (valid(state('group.garage_doors')) && !['closed', 'off'].includes(state('group.garage_doors'))) {
const openGarages = ['cover.large_garage_door', 'cover.small_garage_door']
.filter((entityId) => valid(state(entityId)) && state(entityId) !== 'closed')
.map((entityId) => states[entityId]?.attributes?.friendly_name || pretty(entityId.split('.')[1]));
items.push({
kicker: 'GARAGE',
title: 'Garage door open',
detail: openGarages.join(' · ') || 'Check both garage doors',
icon: 'mdi:garage-open-variant',
accent: '#ef6c00'
});
}
if (state('group.entry_points') === 'on') {
const openEntries = (states['group.entry_points']?.attributes?.entity_id || [])
.filter((entityId) => state(entityId) === 'on')
.map((entityId) => states[entityId]?.attributes?.friendly_name || pretty(entityId.split('.')[1]));
const extraCount = Math.max(0, openEntries.length - 3);
const entryDetail = openEntries.slice(0, 3).join(' · ') + (extraCount ? ` · +${extraCount} more` : '');
items.push({
kicker: 'ENTRY POINT',
title: openEntries.length === 1 ? 'Door or window open' : `${openEntries.length} openings detected`,
detail: entryDetail || 'Check the exterior doors and windows',
icon: 'mdi:door-open',
accent: '#ef6c00',
action: {
service: 'kitchen_kiosk_show_detail',
data: {detail_type: 'entry_points'}
}
});
}
if (valid(state('group.locks')) && state('group.locks') !== 'locked') {
items.push({
kicker: 'SECURITY',
title: 'Check the front door lock',
detail: `Lock group is ${pretty(state('group.locks'))}`,
icon: 'mdi:lock-alert-outline',
accent: '#c62828'
});
}
if (state('binary_sensor.front_door_packages_present') === 'on') {
items.push({
kicker: 'FRONT DOOR',
title: 'Package waiting',
detail: 'Tap for the front door view',
icon: 'mdi:package-variant-closed',
accent: '#8e5a2b',
action: {
service: 'kitchen_kiosk_show_detail',
data: {detail_type: 'package'}
}
});
}
const rheemAlertCount = Number.parseInt(state('sensor.rheem_wh_alert_count'), 10);
const rheemStatus = state('sensor.rheem_wh_diagnostic_status');
const rheemProblem = state('binary_sensor.rheem_wh_problem') === 'on';
if (rheemProblem) {
const activeAlert = state('binary_sensor.rheem_wh_active_alert') === 'on';
const telemetryStale = state('binary_sensor.rheem_wh_telemetry_stale') === 'on';
items.push({
kicker: telemetryStale && !activeAlert ? 'WATER HEATER TELEMETRY' : 'WATER HEATER ALERT',
title: activeAlert && Number.isFinite(rheemAlertCount)
? `${rheemAlertCount} active ${rheemAlertCount === 1 ? 'alert' : 'alerts'}`
: `Rheem status: ${pretty(rheemStatus)}`,
detail: telemetryStale
? 'EcoNet data has been unavailable for 10 minutes · Tap for diagnostics'
: 'Home Assistant Repairs has the diagnostic snapshot · Tap for details',
icon: 'mdi:water-boiler-alert',
accent: telemetryStale && !activeAlert ? '#ef6c00' : '#c62828',
action: {
service: 'kitchen_kiosk_show_detail',
data: {detail_type: 'hot_water'}
}
});
}
const hotWater = Number.parseFloat(state('sensor.rheem_wh_available_hot_water'));
if (!rheemProblem && Number.isFinite(hotWater) && hotWater < 30) {
items.push({
kicker: 'HOT WATER',
title: 'Hot water is running low',
detail: `${Math.round(hotWater)}% available · Tap for recovery details`,
icon: 'mdi:water-thermometer-outline',
accent: '#0277bd',
action: {
service: 'kitchen_kiosk_show_detail',
data: {detail_type: 'hot_water'}
}
});
}
const weekday = now.toLocaleDateString('en-US', {weekday: 'short'});
if (['Wed', 'Sun'].includes(weekday) && state('input_boolean.garbage_cans_out') !== 'on') {
items.push({
kicker: 'TODAY',
title: weekday === 'Wed' ? 'Garbage + recycling day' : 'Garbage day',
detail: 'Put the cans at the curb',
icon: 'mdi:trash-can-outline',
accent: '#2e7d32'
});
}
const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
if (state('input_datetime.water_delivery_date') === dateKey(tomorrow)) {
items.push({
kicker: 'WATER DELIVERY TOMORROW',
title: 'Put the bottles out',
detail: 'Stage them before leaving the house',
icon: 'mdi:bottle-soda-classic-outline',
accent: '#0277bd'
});
}
const battery = Number.parseFloat(state('sensor.spaceship_battery_level'));
const lastClose = new Date(String(state('input_datetime.tesla_model_y_last_garage_close')).replace(' ', 'T'));
const snoozeUntil = new Date(String(state('input_datetime.tesla_model_y_plug_in_snooze_until')).replace(' ', 'T'));
const recentArrival = Number.isFinite(lastClose.getTime()) && (now.getTime() - lastClose.getTime()) < 43200000;
const snoozeExpired = !Number.isFinite(snoozeUntil.getTime()) || snoozeUntil <= now;
if (
Number.isFinite(battery) && battery < 50 && recentArrival && snoozeExpired &&
state('switch.spaceship_charge') === 'off' &&
state('person.carlo') === 'home' && state('person.stacey') === 'home'
) {
items.push({
kicker: 'MODEL Y',
title: 'Plug in the Tesla',
detail: `${Math.round(battery)}% battery after the latest garage arrival`,
icon: 'mdi:ev-plug-tesla',
accent: '#c62828'
});
}
if (state('light.sink') === 'unavailable') {
items.push({
kicker: 'CHECK WALL SWITCH',
title: 'Kitchen sink light',
detail: 'Please turn its physical switch back on.',
icon: 'mdi:lightbulb-alert-outline',
accent: '#ef6c00'
});
}
const saltDays = Number.parseFloat(state('sensor.water_softener_salt_days_since_last_add'));
const saltAverage = Number.parseFloat(state('sensor.water_softener_salt_average_days_between_refills'));
if (
Number.isFinite(saltDays) && Number.isFinite(saltAverage) && saltAverage > 0 &&
saltDays >= saltAverage * 0.9
) {
items.push({
kicker: 'WATER SOFTENER',
title: 'Salt refill coming due',
detail: `${Math.round(saltDays)} days since the last refill`,
icon: 'mdi:shaker-outline',
accent: '#00838f'
});
}
const bowlingHighlights = states['sensor.bearclaw_status_telemetry']?.attributes?.bowlingHighlights;
if (bowlingHighlights?.active && Array.isArray(bowlingHighlights.items)) {
bowlingHighlights.items.forEach((highlight) => {
const expiresAt = new Date(highlight?.expiresAt || '');
const games = (Array.isArray(highlight?.games) ? highlight.games : [])
.map((score) => Number.parseInt(score, 10))
.filter(Number.isFinite);
const highGame = Number.parseInt(highlight?.highGame, 10);
const seriesTotal = Number.parseInt(highlight?.seriesTotal, 10);
if (!Number.isFinite(expiresAt.getTime()) || expiresAt <= now || !games.length || !Number.isFinite(highGame)) return;
const records = new Set(Array.isArray(highlight?.personalBestIds) ? highlight.personalBestIds : []);
const highGameRecord = records.has('bowling_high_game');
const highSeriesRecord = records.has('bowling_high_series') && Number.isFinite(seriesTotal);
const title = highGameRecord
? `${highlight.displayName}'s new high game: ${highGame}`
: (highSeriesRecord
? `${highlight.displayName}'s new high series: ${seriesTotal}`
: `${highlight.displayName}'s high game: ${highGame}`);
const detail = [
games.join(' / '),
Number.isFinite(seriesTotal) ? `${seriesTotal} series` : '',
Number.isFinite(Number(highlight?.weekNum)) ? `Week ${highlight.weekNum}` : ''
].filter(Boolean).join(' · ');
items.push({
kicker: highGameRecord || highSeriesRecord ? 'BOWLING PERSONAL BEST' : 'TUESDAY BOWLING',
title,
detail,
icon: 'mdi:bowling',
accent: '#7e57c2'
});
});
}
const phoneFeed = states['sensor.kiosk_recent_phone_notifications'];
if (Array.isArray(phoneFeed?.attributes?.items)) {
phoneFeed.attributes.items.forEach((notification) => {
if (Number(notification.expires_at) * 1000 <= now.getTime()) return;
items.push({
kicker: `PHONE NOTIFICATION · ${notification.sent_time}`,
title: notification.title,
detail: notification.message,
icon: notification.icon,
accent: notification.color
});
});
}
const recordFeed = states['sensor.kiosk_recent_personal_records'];
const personalRecords = recordFeed?.state === 'ready' ? recordFeed.attributes?.items : null;
if (Array.isArray(personalRecords)) {
personalRecords.forEach((record) => {
const occurredAt = new Date(record?.occurredAt || '');
if (!Number.isFinite(occurredAt.getTime()) ||
occurredAt > now || now - occurredAt > 7 * 24 * 60 * 60 * 1000 ||
!record?.person || !record?.label || !record?.displayValue) return;
items.push({
kicker: 'NEW PERSONAL BEST',
title: `${record.person}: ${record.label}`,
detail: [record.displayValue, record.activityType, record.dayKey].filter(Boolean).join(' · '),
icon: 'mdi:trophy-outline',
accent: '#2e7d32'
});
});
}
const cruiseDays = Number.parseInt(state('sensor.next_booked_cruise_countdown'), 10);
const cruiseEntity = states['sensor.next_booked_cruise_countdown'];
const cruiseShip = String(cruiseEntity?.attributes?.ship || '').trim();
const cruiseDate = String(cruiseEntity?.attributes?.sail_date || '').trim();
if (Number.isInteger(cruiseDays) && cruiseDays >= 0 && cruiseShip && cruiseDate) {
const sailDate = new Date(`${cruiseDate}T12:00:00`);
const sailLabel = Number.isFinite(sailDate.getTime())
? sailDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
: cruiseDate;
const cruiseNights = Number.parseInt(cruiseEntity?.attributes?.nights, 10);
const cruisePort = String(cruiseEntity?.attributes?.port || '').trim();
const detail = [
`Departs ${sailLabel}`,
Number.isInteger(cruiseNights) && cruiseNights > 0
? `${cruiseNights} ${cruiseNights === 1 ? 'night' : 'nights'}`
: '',
cruisePort
].filter(Boolean).join(' · ');
items.push({
kicker: 'NEXT BOOKED CRUISE',
title: cruiseDays === 0
? `${cruiseShip} sails today`
: `${cruiseDays} ${cruiseDays === 1 ? 'day' : 'days'} until ${cruiseShip}`,
detail,
icon: 'mdi:ferry',
accent: '#00838f'
});
}
[
['Carlo', 'person.carlo', 'sensor.carlo_location_display'],
['Stacey', 'person.stacey', 'sensor.stacey_location_display'],
['Justin', 'person.justin', 'sensor.justin_location_display'],
['Paige', 'person.paige', 'sensor.paige_location_display']
].forEach(([name, personId, locationId]) => {
const personState = state(personId);
if (valid(personState) && personState !== 'home') {
const locationEntity = states[locationId];
const rawLocation = pretty(valid(state(locationId)) ? state(locationId) : personState);
const withoutName = rawLocation.replace(new RegExp(`^${name}\\s+`, 'i'), '');
const sinceMatch = withoutName.match(/\s*\(since\s+([^)]+)\)\s*$/i);
const location = withoutName.replace(/\s*\(since\s+[^)]+\)\s*$/i, '').trim();
const changedAt = new Date(locationEntity?.last_changed || states[personId]?.last_changed);
const since = sinceMatch?.[1] || (
Number.isFinite(changedAt.getTime())
? changedAt.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })
: 'recently'
);
items.push({
kicker: 'FAMILY LOCATION',
title: `${name} - ${location || pretty(personState)}`,
detail: `Since ${since}`,
icon: 'mdi:map-marker-account-outline',
accent: '#6a1b9a',
action: {
service: 'kitchen_kiosk_show_detail',
data: {detail_type: 'family', detail_subject: personId}
}
});
}
});
if (items.length === 0) {
items.push({
kicker: 'HOUSEHOLD',
title: 'Everything is handled',
detail: 'Everyone is home and there are no active reminders',
icon: 'mdi:check-circle-outline',
accent: '#2e7d32'
});
}
const formatSince = (entity) => {
const changed = new Date(entity?.last_changed);
if (!Number.isFinite(changed.getTime())) return 'recently';
@ -800,6 +317,8 @@ kiosk_ambient_alerts:
line-height: 1.1;
letter-spacing: 0.1em;
}
.kitchen-ambient-rotator.seasonal .kitchen-ambient-kicker { padding-right: 92px; }
.kitchen-ambient-rotator.compact.seasonal .kitchen-ambient-kicker { padding-right: 56px; }
.kitchen-ambient-title {
margin-top: 6px;
color: var(--primary-text-color);
@ -1074,7 +593,7 @@ kiosk_ambient_alerts:
.kitchen-detail-overlay.compact .kitchen-detail-hot-percent { font-size: 90px; }
.kitchen-detail-overlay.compact .kitchen-detail-hot-percent span { font-size: 44px; }
</style>
<div class="kitchen-ambient-rotator ${variables.compact ? 'compact' : ''}" style=${detailActive ? 'visibility:hidden' : ''} @touchend=${handleAmbientTap} @click=${handleAmbientTap}>
<div class="kitchen-ambient-rotator ${variables.compact ? 'compact' : ''} ${seasonal.mode !== 'standard' ? 'seasonal' : ''}" style=${detailActive ? 'visibility:hidden' : ''} @touchend=${handleAmbientTap} @click=${handleAmbientTap}>
${items.map((item, index) => html`
<div
class="kitchen-ambient-slide ${items.length === 1 ? 'single' : ''}"
@ -1099,8 +618,15 @@ kiosk_ambient_alerts:
- overflow: hidden
- border-radius: 18px
- background: >
[[[ return states['timer.kitchen_kiosk_detail_display']?.state === 'active' ? 'transparent'
: 'linear-gradient(120deg, rgba(33, 150, 243, 0.08), rgba(103, 58, 183, 0.08))'; ]]]
[[[
if (states['timer.kitchen_kiosk_detail_display']?.state === 'active') return 'transparent';
const seasonal = window.BearStoneKioskAmbient?.seasonal?.(states);
return seasonal && seasonal.wash !== 'none' ? (variables.compact ? seasonal.compactBackground : seasonal.background)
: 'linear-gradient(120deg, rgba(33, 150, 243, 0.08), rgba(103, 58, 183, 0.08))';
]]]
- box-shadow: >
[[[ return states['timer.kitchen_kiosk_detail_display']?.state === 'active' ? 'none'
: window.BearStoneKioskAmbient?.seasonal?.(states)?.edge || 'none'; ]]]
grid:
- grid-template-areas: '"ambient"'
- grid-template-columns: 1fr

@ -34,6 +34,7 @@ cards:
grid-template-columns: repeat(4, minmax(0, 1fr))
grid-gap: 4px
cards:
- !include /config/dashboards/infrastructure/partials/informational_notices.yaml
- type: custom:button-card
template: bearstone_overview_ribbon_control
entity: sensor.infra_dashboard_active_issue_count

@ -8,6 +8,7 @@
# -------------------------------------------------------------------
# Notes: Visible second Overview tab, after Home and before Camera.
# Notes: Lighting menu and All Lights Off share the first quick-control row.
# Notes: The top navigation tab uses the phone icon without a label.
# Notes: Tap access and climate tiles for Home Assistant's native controls.
# Notes: Two-column 112px tiles and large text prioritize use without glasses.
# Notes: Odd final tiles span both columns to avoid small orphan controls.
@ -20,7 +21,7 @@
path: mobile
title: Mobile
icon: mdi:cellphone
show_icon_and_title: true
show_icon_and_title: false
type: sections
max_columns: 4
dense_section_placement: true
@ -47,6 +48,7 @@ sections:
grid-gap: 12px
margin: 0
cards:
- !include /config/dashboards/infrastructure/partials/informational_notices.yaml
- type: custom:button-card
template: bearstone_nav_back
name: Back to Home

Loading…
Cancel
Save

Powered by TurnKey Linux.