package settings // R-388 slice 1 (`08-alarm-ladder.md` §8.1, `09` §3 decision 200; operator answers 2026-10-12): // the notification page shows THREE group switches instead of one switch per detector. A group is a // view over the SAME `enabled_events` list — saving a group writes all its detectors into it, so // nothing on the wire and nothing on the hub changes. // // Every detector the page can save belongs to EXACTLY ONE group. Pinned by // TestR388_EveryPageSwitchIsInExactlyOneGroup (web), which reads the rendered page's switches and // fails when a new detector gets a checkbox but no group. // Notification group keys (also the form-field stems `group_` / `group__shown`). const ( NotifGroupMachine = "machine" // „A gépem és a meghajtóim" — the household acts NotifGroupApps = "apps" // „Az alkalmazásaim" NotifGroupHandled = "handled" // „Amit mi intézünk" — we act; the operator always gets these ) // Group states as the page renders them. const ( NotifGroupOn = "on" NotifGroupOff = "off" NotifGroupCustom = "custom" // some of its detectors on, some off — „egyedi" ) // NotificationGroup is one switch on the notification page and the detectors behind it. type NotificationGroup struct { Key string Events []string } // NotificationGroups is the §8.1 membership table, in page order. var NotificationGroups = []NotificationGroup{ {Key: NotifGroupMachine, Events: []string{ "storage_disconnected", "storage_reconnected", "disk_warning", "disk_critical", "offbox_enlarge_blocked", }}, {Key: NotifGroupApps, Events: []string{ "app_stopped_unhealthy", "app_start_failed", "app_update_held", "app_update_undone", }}, {Key: NotifGroupHandled, Events: []string{ "backup_failed", "db_dump_failed", "backup_integrity_failed", "crossdrive_failed", "expected_backup_missed", "expected_dbdump_missed", "node_down", "health_critical", "health_recovered", }}, } // NotificationGroupByKey returns the group with this key, or false. func NotificationGroupByKey(key string) (NotificationGroup, bool) { for _, g := range NotificationGroups { if g.Key == key { return g, true } } return NotificationGroup{}, false } // NotificationGroupState reports whether all, none or some of the group's detectors are in enabled. // An unknown key reads as off (renders unchecked, and the save leaves the details authoritative). func NotificationGroupState(enabled []string, key string) string { g, ok := NotificationGroupByKey(key) if !ok { return NotifGroupOff } set := make(map[string]bool, len(enabled)) for _, e := range enabled { set[e] = true } on := 0 for _, e := range g.Events { if set[e] { on++ } } switch { case on == len(g.Events): return NotifGroupOn case on == 0: return NotifGroupOff default: return NotifGroupCustom } } // GroupState is the template's view of NotificationGroupState over this household's list. // VALUE receiver on purpose: a pointer receiver compiles and passes vet, then 500s at render // (`.claude/rules/ui-hungarian.md`). func (p NotificationPrefs) GroupState(key string) string { return NotificationGroupState(p.EnabledEvents, key) }