Top (alarm stats broken, tables full of junk columns/rows, motion tiles unreadable):
Bottom (covers missing/duplicated, batteries duplicated and sorted uselessly):
After
Top:
Bottom:
Review report
Critical
Alarm stats could never show their alert state. All four stat panels used homeassistant_alarm_control_panel_state{state="X"} == 1. The == 1 comparison drops series whose value is 0, so "Armed Home", "Armed Away" and — worst — "Triggered" showed "No data" whenever they were not active, and the 0 → "OK"/"Not Active" value mappings never fired. A break-in would have rendered the Triggered panel green "No data" until the refresh after the trigger, and the panel gave no signal at all in the normal state. Additionally, "Disarmed" rendered 5 overlapping series (one per alarm panel: erdgeschoss, obergeschoss, dachgeschoss, treppenhaus, udm_sudstrasse_28_alarm_manager) as unreadable stacked text. ✅ FIXED — min(...{state="disarmed"}) (1 = ALL 5 panels disarmed) and max(...{state="armed_home"/"armed_away"/"triggered"}) (1 = ANY panel in that state). The one-hot export guarantees every state series always exists with 0/1, so each tile now always shows a real value. Titles clarified to "(all panels)" / "(any panel)".
diagnostics (Intrusion, Magnetic field interference, System software failure, tilt-position),
_2 duplicate twin entities,
and via .*Garten.*/[Tt]ur even the pond-pump Shelly ("Garten Schuppen Shelly Over-current"), the fridge door and "Overtemperature" sensors.
The tables also displayed raw scrape labels (container/endpoint/namespace/pod/service) as columns. ✅ FIXED — curated entity lists with ONE canonical is-open entity per physical sensor (11 windows, 9 doors), verified against HA device classes (window/door). max by (entity, friendly_name) collapses scrape labels (and dedupes any scrape residue), so the tables now show Sensor / Name / Status only. binary_sensor.dg_buro_fenster was excluded because it is a group of the DG Nord+Süd sensors (would double-count).
Major
Motion panels missed more than half the detectors. The regex .*[Bb]ewegungs.*[Oo]ccupancy.* required both words in order, catching only 5 sensors. It missed: eg_eingang_bewegunsmelder_occupancy (typo "Bewegunsmelder" in the friendly name!), all *_motion-class sensors (EG/DG Treppenhaus, Haustür doorbell), and all mmWave Präsenzmelder (Büro, Küche, Wickelkommode, Werkstatt). It also produced duplicated legend entries from scrape residue. ✅ FIXED — curated list of 13 physical detectors for both the stat panel and the history chart, deduped with max by. The stat panel was also switched from orientation: horizontal (overlapping unreadable rows) to grid tiles with name+value.
Covers panel: name-based exclusion dropped 8 physical covers but kept a group.friendly_name!~".*Alle.*|.*Süd.*|.*[Nn]ord.*|.*[Oo]st.*|.*[Ww]est.*" was meant to exclude the group covers, but the physical covers are named "Nord-Rollo", "Ost-Rollo", "Süd-Rollo", "EG Schlafzimmer Rollo Nord/West", "Garagentor West" — all excluded. Meanwhile cover.eg_schlafzimmer_rollo and cover.eg_kuche_rollo (verified via HA attributes: they are groups with entity_id members) slipped through. Result: 6 of 14 rows, one of them a group average, and the garage door — the most security-relevant cover — invisible. ✅ FIXED — exclude the 5 group covers by entity (alle_rollos, ost_rollos, sud_rollos, eg_kuche_rollo, eg_schlafzimmer_rollo); all 13 physical covers incl. cover.wave_1_garage_garagentor_west now show. Bars are labelled by entity ID because friendly names repeat ("Rollo" ×3, "Ost-Rollo" ×3).
showed every device twice (the battery_notes integration creates *_batteryand*_battery_plus twins — this is not scrape residue),
> 0 would hide a battery at exactly 0 % — the single most important row,
sort_desc put the healthy 100 % batteries on top and the dying ones at the bottom of an internal scroll,
included the Vitocharge PV storage module SoCs (infrastructure, not device batteries). ✅ FIXED — sort(max by (entity, friendly_name) (...{entity!~".*_battery_plus.*|sensor.vitocharge.*"})): deduped, ascending (1 % Konsti, 5 % OG Treppenhaus Bewegungsmelder, 8 % Treppenhaus/OG switches at the very top), no > 0 cutoff, panel height increased 10→14.
Minor
Stale values on "current value" panels. All stat/bargauge panels used range queries with lastNotNull, so a dead sensor kept showing its last value as if live. ✅ FIXED — "instant": true on all current-value panels (alarm stats, motion status, covers, batteries); dead sensors now honestly disappear / show No data within Prometheus' 5-min staleness window.
Hardware / data findings (not paper-overable by queries)
binary_sensor.dg_buro_fenstersensor_sud_opening (DG Büro Südfenster) is unavailable in HA and binary_sensor.eg_garderobe_fenster_open is unknown — both are in the curated Windows list but currently return no data (the table shows 9 of 11 rows). Likely dead batteries / dropped from Zigbee.
Doorbell/camera motion (haustur_entry_motion, G5 Dome, G6 Instant) and kg_werkstatt_prasenzmelder are unavailable (UniFi Protect / EPL offline?). haustur_entry_motion and the Werkstatt Präsenzmelder are included in the curated motion list and will appear when they come back; the two cameras were left out deliberately.
OG/KG have no window/door contact sensors at all — the tables honestly cover only what exists (EG + DG Büro + Haustür + Kellertür Heizraum).
❓ Questions for the owner
binary_sensor.treppenhaus_motion ("Treppenhaus Motion") exists in HA but I could not find its definition in the homeassistant repo (not in templates/packages/automations). It looks like a template/aggregate over the three Treppenhaus detectors, so I left it OUT of the motion list to avoid double-counting. If it is actually a separate physical sensor, say the word and I'll add it.
The Doors table includes interior doors (EG Bad, EG Schlafzimmer, Garderobe, DG Büro). Keep them, or restrict the panel to perimeter doors (Haustür, Wohnungstür, Garten-/Terrassentür, Kellertür)?
"Disarmed (all panels)" includes the UniFi alarm manager (udm_sudstrasse_28_alarm_manager) in the ALL-disarmed check. If that panel is ever left armed permanently, the tile would stay "Not Active" — restrict to the 4 Alarmo panels if so.
Verification
Every query was validated against live Prometheus (port-forward, instant queries) before editing:
homeassistant_alarm_control_panel_state: confirmed one-hot across 5 panels × 10 states; min(disarmed)=1, max(armed_home/armed_away/triggered)=0 at review time.
Curated window/door entities: cross-checked against HA /api/states filtered by device_class in (window, door, opening); group membership of dg_buro_fenster, eg_kuche_rollo, eg_schlafzimmer_rollo confirmed via their entity_id attributes.
Motion list: cross-checked against HA device_class motion/occupancy/presence; magic_areas aggregates, BLE trackers, per-zone mmWave sub-sensors and phone presence excluded.
Battery query: returned 61 deduped series, ascending, *_battery_plus twins and Vitocharge SoCs gone.
# Home Security Details — dashboard review & fix
## Before
Top (alarm stats broken, tables full of junk columns/rows, motion tiles unreadable):

Bottom (covers missing/duplicated, batteries duplicated and sorted uselessly):

## After
Top:

Bottom:

## Review report
### Critical
1. **Alarm stats could never show their alert state.** All four stat panels used `homeassistant_alarm_control_panel_state{state="X"} == 1`. The `== 1` comparison *drops* series whose value is 0, so "Armed Home", "Armed Away" and — worst — "**Triggered**" showed **"No data"** whenever they were not active, and the `0 → "OK"/"Not Active"` value mappings never fired. A break-in would have rendered the Triggered panel green "No data" until the refresh after the trigger, and the panel gave no signal at all in the normal state. Additionally, "Disarmed" rendered 5 overlapping series (one per alarm panel: erdgeschoss, obergeschoss, dachgeschoss, treppenhaus, udm_sudstrasse_28_alarm_manager) as unreadable stacked text.
✅ FIXED — `min(...{state="disarmed"})` (1 = ALL 5 panels disarmed) and `max(...{state="armed_home"/"armed_away"/"triggered"})` (1 = ANY panel in that state). The one-hot export guarantees every state series always exists with 0/1, so each tile now always shows a real value. Titles clarified to "(all panels)" / "(any panel)".
2. **Windows/Doors tables: wrong sensors, inverted semantics, junk columns.** The friendly-name regexes (`.*[Ff]enster.*`, `.*[Tt](ü|u)r.*|...|.*Garten.*`) matched:
- HmIP `*_window_door_is_closed` entities where **1 = CLOSED** → shown as "OPEN" (inverted!),
- battery sensors (`Haustür Schalter Battery low`, `EG Bad Fenster Battery low`, …),
- diagnostics (Intrusion, Magnetic field interference, System software failure, tilt-position),
- `_2` duplicate twin entities,
- and via `.*Garten.*`/`[Tt]ur` even the pond-pump Shelly ("Garten Schuppen Shelly Over-current"), the fridge door and "Overtemperature" sensors.
The tables also displayed raw scrape labels (container/endpoint/namespace/pod/service) as columns.
✅ FIXED — curated entity lists with ONE canonical is-open entity per physical sensor (11 windows, 9 doors), verified against HA device classes (`window`/`door`). `max by (entity, friendly_name)` collapses scrape labels (and dedupes any scrape residue), so the tables now show Sensor / Name / Status only. `binary_sensor.dg_buro_fenster` was excluded because it is a **group** of the DG Nord+Süd sensors (would double-count).
### Major
3. **Motion panels missed more than half the detectors.** The regex `.*[Bb]ewegungs.*[Oo]ccupancy.*` required both words in order, catching only 5 sensors. It missed: `eg_eingang_bewegunsmelder_occupancy` (typo "Bewegunsmelder" in the friendly name!), all `*_motion`-class sensors (EG/DG Treppenhaus, Haustür doorbell), and all mmWave Präsenzmelder (Büro, Küche, Wickelkommode, Werkstatt). It also produced duplicated legend entries from scrape residue.
✅ FIXED — curated list of 13 physical detectors for both the stat panel and the history chart, deduped with `max by`. The stat panel was also switched from `orientation: horizontal` (overlapping unreadable rows) to grid tiles with name+value.
4. **Covers panel: name-based exclusion dropped 8 physical covers but kept a group.** `friendly_name!~".*Alle.*|.*Süd.*|.*[Nn]ord.*|.*[Oo]st.*|.*[Ww]est.*"` was meant to exclude the group covers, but the *physical* covers are named "Nord-Rollo", "Ost-Rollo", "Süd-Rollo", "EG Schlafzimmer Rollo Nord/West", "Garagentor West" — all excluded. Meanwhile `cover.eg_schlafzimmer_rollo` and `cover.eg_kuche_rollo` (verified via HA attributes: they are **groups** with `entity_id` members) slipped through. Result: 6 of 14 rows, one of them a group average, and the **garage door** — the most security-relevant cover — invisible.
✅ FIXED — exclude the 5 group covers by entity (`alle_rollos`, `ost_rollos`, `sud_rollos`, `eg_kuche_rollo`, `eg_schlafzimmer_rollo`); all 13 physical covers incl. `cover.wave_1_garage_garagentor_west` now show. Bars are labelled by entity ID because friendly names repeat ("Rollo" ×3, "Ost-Rollo" ×3).
5. **Battery panel: twins, hidden dead batteries, useless sort order.** `sort_desc(homeassistant_sensor_battery_percent > 0)`:
- showed every device twice (the `battery_notes` integration creates `*_battery` **and** `*_battery_plus` twins — this is not scrape residue),
- `> 0` would hide a battery at exactly 0 % — the single most important row,
- `sort_desc` put the healthy 100 % batteries on top and the dying ones at the bottom of an internal scroll,
- included the Vitocharge PV storage module SoCs (infrastructure, not device batteries).
✅ FIXED — `sort(max by (entity, friendly_name) (...{entity!~".*_battery_plus.*|sensor.vitocharge.*"}))`: deduped, ascending (1 % Konsti, 5 % OG Treppenhaus Bewegungsmelder, 8 % Treppenhaus/OG switches at the very top), no `> 0` cutoff, panel height increased 10→14.
### Minor
6. **Stale values on "current value" panels.** All stat/bargauge panels used range queries with `lastNotNull`, so a dead sensor kept showing its last value as if live.
✅ FIXED — `"instant": true` on all current-value panels (alarm stats, motion status, covers, batteries); dead sensors now honestly disappear / show No data within Prometheus' 5-min staleness window.
## Hardware / data findings (not paper-overable by queries)
- `binary_sensor.dg_buro_fenstersensor_sud_opening` (DG Büro Südfenster) is **unavailable** in HA and `binary_sensor.eg_garderobe_fenster_open` is **unknown** — both are in the curated Windows list but currently return no data (the table shows 9 of 11 rows). Likely dead batteries / dropped from Zigbee.
- Doorbell/camera motion (`haustur_entry_motion`, G5 Dome, G6 Instant) and `kg_werkstatt_prasenzmelder` are **unavailable** (UniFi Protect / EPL offline?). `haustur_entry_motion` and the Werkstatt Präsenzmelder are included in the curated motion list and will appear when they come back; the two cameras were left out deliberately.
- Batteries needing replacement soon: **EG Schlafzimmer Konsti 1 %**, OG Treppenhaus Bewegungsmelder 5 %, OG Treppenhaus Schalter 8 %, Treppenhaus Haustür Schalter 1/2 8–8.5 %, DG Treppenhaus Schalter 11 %.
- OG/KG have **no window/door contact sensors** at all — the tables honestly cover only what exists (EG + DG Büro + Haustür + Kellertür Heizraum).
## ❓ Questions for the owner
1. `binary_sensor.treppenhaus_motion` ("Treppenhaus Motion") exists in HA but I could not find its definition in the homeassistant repo (not in templates/packages/automations). It looks like a template/aggregate over the three Treppenhaus detectors, so I left it OUT of the motion list to avoid double-counting. If it is actually a separate physical sensor, say the word and I'll add it.
2. The Doors table includes interior doors (EG Bad, EG Schlafzimmer, Garderobe, DG Büro). Keep them, or restrict the panel to perimeter doors (Haustür, Wohnungstür, Garten-/Terrassentür, Kellertür)?
3. "Disarmed (all panels)" includes the UniFi alarm manager (`udm_sudstrasse_28_alarm_manager`) in the ALL-disarmed check. If that panel is ever left armed permanently, the tile would stay "Not Active" — restrict to the 4 Alarmo panels if so.
## Verification
Every query was validated against live Prometheus (port-forward, instant queries) before editing:
- `homeassistant_alarm_control_panel_state`: confirmed one-hot across 5 panels × 10 states; `min(disarmed)=1`, `max(armed_home/armed_away/triggered)=0` at review time.
- Curated window/door entities: cross-checked against HA `/api/states` filtered by `device_class` in (window, door, opening); group membership of `dg_buro_fenster`, `eg_kuche_rollo`, `eg_schlafzimmer_rollo` confirmed via their `entity_id` attributes.
- Motion list: cross-checked against HA device_class motion/occupancy/presence; magic_areas aggregates, BLE trackers, per-zone mmWave sub-sensors and phone presence excluded.
- Battery query: returned 61 deduped series, ascending, `*_battery_plus` twins and Vitocharge SoCs gone.
- Modified JSON rendered via temp preview uid `home-security-preview` (screenshots above), preview deleted afterwards.
- Alarm stats: '== 1' dropped the series when false, so Armed Home/Away and
Triggered could only ever show 'No data' and never their 0-state; Disarmed
rendered 5 overlapping series. Use min()/max() aggregation over the one-hot
alarm_control_panel_state series so every tile always shows a real 0/1.
- Windows/Doors tables: replace friendly-name regexes (matched battery-low
sensors, HmIP 'Window/door is CLOSED' entities with inverted semantics,
intrusion/tamper diagnostics, _2 twins, pond-pump/fridge sensors) with a
curated entity list: one canonical is-open entity per physical sensor.
Collapse scrape labels via max by so the tables show Sensor/Name/Status.
- Motion panels: old regex required 'Bewegungs.*Occupancy' and missed the
Eingang sensor (typo 'Bewegunsmelder'), all motion-class sensors and the
mmWave Praesenzmelder. Curated list of physical detectors.
- Covers: name-based exclusion (.*Nord.*|.*Ost.* etc.) dropped 8 physical
covers incl. the garage door while letting the eg_schlafzimmer/eg_kuche
group entities through. Exclude the 5 group covers by entity instead;
label bars by entity because friendly names repeat ('Rollo' x3).
- Batteries: filter battery_notes '+' twin entities, drop PV storage SoC,
remove '> 0' (which would hide a 0% dead battery), sort ascending so the
most critical batteries are on top, instant query so stale values drop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Home Security Details — dashboard review & fix
Before
Top (alarm stats broken, tables full of junk columns/rows, motion tiles unreadable):

Bottom (covers missing/duplicated, batteries duplicated and sorted uselessly):

After
Top:

Bottom:

Review report
Critical
Alarm stats could never show their alert state. All four stat panels used
homeassistant_alarm_control_panel_state{state="X"} == 1. The== 1comparison drops series whose value is 0, so "Armed Home", "Armed Away" and — worst — "Triggered" showed "No data" whenever they were not active, and the0 → "OK"/"Not Active"value mappings never fired. A break-in would have rendered the Triggered panel green "No data" until the refresh after the trigger, and the panel gave no signal at all in the normal state. Additionally, "Disarmed" rendered 5 overlapping series (one per alarm panel: erdgeschoss, obergeschoss, dachgeschoss, treppenhaus, udm_sudstrasse_28_alarm_manager) as unreadable stacked text.✅ FIXED —
min(...{state="disarmed"})(1 = ALL 5 panels disarmed) andmax(...{state="armed_home"/"armed_away"/"triggered"})(1 = ANY panel in that state). The one-hot export guarantees every state series always exists with 0/1, so each tile now always shows a real value. Titles clarified to "(all panels)" / "(any panel)".Windows/Doors tables: wrong sensors, inverted semantics, junk columns. The friendly-name regexes (
.*[Ff]enster.*,.*[Tt](ü|u)r.*|...|.*Garten.*) matched:*_window_door_is_closedentities where 1 = CLOSED → shown as "OPEN" (inverted!),Haustür Schalter Battery low,EG Bad Fenster Battery low, …),_2duplicate twin entities,.*Garten.*/[Tt]ureven the pond-pump Shelly ("Garten Schuppen Shelly Over-current"), the fridge door and "Overtemperature" sensors.The tables also displayed raw scrape labels (container/endpoint/namespace/pod/service) as columns.
✅ FIXED — curated entity lists with ONE canonical is-open entity per physical sensor (11 windows, 9 doors), verified against HA device classes (
window/door).max by (entity, friendly_name)collapses scrape labels (and dedupes any scrape residue), so the tables now show Sensor / Name / Status only.binary_sensor.dg_buro_fensterwas excluded because it is a group of the DG Nord+Süd sensors (would double-count).Major
Motion panels missed more than half the detectors. The regex
.*[Bb]ewegungs.*[Oo]ccupancy.*required both words in order, catching only 5 sensors. It missed:eg_eingang_bewegunsmelder_occupancy(typo "Bewegunsmelder" in the friendly name!), all*_motion-class sensors (EG/DG Treppenhaus, Haustür doorbell), and all mmWave Präsenzmelder (Büro, Küche, Wickelkommode, Werkstatt). It also produced duplicated legend entries from scrape residue.✅ FIXED — curated list of 13 physical detectors for both the stat panel and the history chart, deduped with
max by. The stat panel was also switched fromorientation: horizontal(overlapping unreadable rows) to grid tiles with name+value.Covers panel: name-based exclusion dropped 8 physical covers but kept a group.
friendly_name!~".*Alle.*|.*Süd.*|.*[Nn]ord.*|.*[Oo]st.*|.*[Ww]est.*"was meant to exclude the group covers, but the physical covers are named "Nord-Rollo", "Ost-Rollo", "Süd-Rollo", "EG Schlafzimmer Rollo Nord/West", "Garagentor West" — all excluded. Meanwhilecover.eg_schlafzimmer_rolloandcover.eg_kuche_rollo(verified via HA attributes: they are groups withentity_idmembers) slipped through. Result: 6 of 14 rows, one of them a group average, and the garage door — the most security-relevant cover — invisible.✅ FIXED — exclude the 5 group covers by entity (
alle_rollos,ost_rollos,sud_rollos,eg_kuche_rollo,eg_schlafzimmer_rollo); all 13 physical covers incl.cover.wave_1_garage_garagentor_westnow show. Bars are labelled by entity ID because friendly names repeat ("Rollo" ×3, "Ost-Rollo" ×3).Battery panel: twins, hidden dead batteries, useless sort order.
sort_desc(homeassistant_sensor_battery_percent > 0):battery_notesintegration creates*_batteryand*_battery_plustwins — this is not scrape residue),> 0would hide a battery at exactly 0 % — the single most important row,sort_descput the healthy 100 % batteries on top and the dying ones at the bottom of an internal scroll,✅ FIXED —
sort(max by (entity, friendly_name) (...{entity!~".*_battery_plus.*|sensor.vitocharge.*"})): deduped, ascending (1 % Konsti, 5 % OG Treppenhaus Bewegungsmelder, 8 % Treppenhaus/OG switches at the very top), no> 0cutoff, panel height increased 10→14.Minor
lastNotNull, so a dead sensor kept showing its last value as if live.✅ FIXED —
"instant": trueon all current-value panels (alarm stats, motion status, covers, batteries); dead sensors now honestly disappear / show No data within Prometheus' 5-min staleness window.Hardware / data findings (not paper-overable by queries)
binary_sensor.dg_buro_fenstersensor_sud_opening(DG Büro Südfenster) is unavailable in HA andbinary_sensor.eg_garderobe_fenster_openis unknown — both are in the curated Windows list but currently return no data (the table shows 9 of 11 rows). Likely dead batteries / dropped from Zigbee.haustur_entry_motion, G5 Dome, G6 Instant) andkg_werkstatt_prasenzmelderare unavailable (UniFi Protect / EPL offline?).haustur_entry_motionand the Werkstatt Präsenzmelder are included in the curated motion list and will appear when they come back; the two cameras were left out deliberately.❓ Questions for the owner
binary_sensor.treppenhaus_motion("Treppenhaus Motion") exists in HA but I could not find its definition in the homeassistant repo (not in templates/packages/automations). It looks like a template/aggregate over the three Treppenhaus detectors, so I left it OUT of the motion list to avoid double-counting. If it is actually a separate physical sensor, say the word and I'll add it.udm_sudstrasse_28_alarm_manager) in the ALL-disarmed check. If that panel is ever left armed permanently, the tile would stay "Not Active" — restrict to the 4 Alarmo panels if so.Verification
Every query was validated against live Prometheus (port-forward, instant queries) before editing:
homeassistant_alarm_control_panel_state: confirmed one-hot across 5 panels × 10 states;min(disarmed)=1,max(armed_home/armed_away/triggered)=0at review time./api/statesfiltered bydevice_classin (window, door, opening); group membership ofdg_buro_fenster,eg_kuche_rollo,eg_schlafzimmer_rolloconfirmed via theirentity_idattributes.*_battery_plustwins and Vitocharge SoCs gone.home-security-preview(screenshots above), preview deleted afterwards.- Alarm stats: '== 1' dropped the series when false, so Armed Home/Away and Triggered could only ever show 'No data' and never their 0-state; Disarmed rendered 5 overlapping series. Use min()/max() aggregation over the one-hot alarm_control_panel_state series so every tile always shows a real 0/1. - Windows/Doors tables: replace friendly-name regexes (matched battery-low sensors, HmIP 'Window/door is CLOSED' entities with inverted semantics, intrusion/tamper diagnostics, _2 twins, pond-pump/fridge sensors) with a curated entity list: one canonical is-open entity per physical sensor. Collapse scrape labels via max by so the tables show Sensor/Name/Status. - Motion panels: old regex required 'Bewegungs.*Occupancy' and missed the Eingang sensor (typo 'Bewegunsmelder'), all motion-class sensors and the mmWave Praesenzmelder. Curated list of physical detectors. - Covers: name-based exclusion (.*Nord.*|.*Ost.* etc.) dropped 8 physical covers incl. the garage door while letting the eg_schlafzimmer/eg_kuche group entities through. Exclude the 5 group covers by entity instead; label bars by entity because friendly names repeat ('Rollo' x3). - Batteries: filter battery_notes '+' twin entities, drop PV storage SoC, remove '> 0' (which would hide a 0% dead battery), sort ascending so the most critical batteries are on top, instant query so stale values drop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>