# R-79 — health issue and warning text on the household's pages and mail: a one-page design (2026-10-08) **Status:** design only. Read in source today (controller `main` a0370b4ed8ef, hub `main` b2dce901b23f). Architecture document: `architecture/10-localisation.md` (§9 „compared, not shown", §10.1 „on the wire = not translated", §10.6b residue table). ## The row is half stale — what moved since it was filed The row (filed 2026-07-26) says „every producer is English" and asks for a spike to pick the seam. **The seam was picked and built** by R-516 item 10 (controller, after 0.252.0): the wire text stays frozen (`internal/monitor/wire_golden_test.go`) and each entry carries a parallel `MsgRef` (bundle key + args) that the dashboard banner renders in the household's language (`internal/monitor/healthcheck.go:28-35, 73-86`; `internal/web/alerts.go:238-279`, `MessageKey`/`MessageArgs` rendered by `GetAlerts(lang)`). That is the render-boundary seam, and it is the right one: the hub report keeps its bytes. **Done (key attached, pinned by `internal/monitor/r516_resource_msgs_test.go`):** all 7 resource entries — system/data disk high + critical, memory, CPU, temperature (`healthcheck.go:357-441`). **Still verbatim on the household's banner (zero `MsgRef`):** | producer | `healthcheck.go` | wire text | what a household sees | |---|---|---|---| | Docker unreachable | 138 | `Docker: %v` (English) | English in both languages | | protected container down | 157 | `Protected container not running: %s` (English) | English in both languages | | storage almost full (issue) | 170 via `checkStoragePaths` 336 | `issueFmtStorageAlmostFull` (Hungarian, frozen) | Hungarian for an English household | | storage unavailable / not separate / usage high (warnings) | 173 via 322, 328, 338 | `warnFmtStorage*` (Hungarian, frozen) | Hungarian for an English household | (The disconnected-drive warning is already replaced by its own banner, `alerts.go:254-261`.) **The mail half, not covered by R-516:** `health_critical` is customer-on by default (`controller internal/settings/settings.go:635`). The controller sends `HealthDetails{issues, warnings}` as the event's details (`internal/notify/notifier.go:139-144, 383-396`). The hub's customer mail localises the headline (`mail.event.health_critical`) but then appends the **raw details JSON** as „- Megjegyzés: {…}" (`hub internal/notify/templates.go:187-189`, called from `dispatcher.go:331, 880`). So the household's mail carries `{"previous_status":"ok","current_status":"fail","issues":["Protected container not running: cloudflared"]}`. ## Options | | What | Costs | Risk | |---|---|---|---| | **A** | Dashboard only: give the 6 remaining producers a `MsgRef` (`checkStoragePaths` returns a parallel `[]MsgRef`); 6 hu+en keys whose Hungarian equals today's text. | ~2 h, controller only. | None on the wire (the wire golden stays green by construction). | | **B** | A + mail: the hub stops appending the raw details JSON to the CUSTOMER mail of `health_degraded` / `health_critical` / `health_recovered` (operator mail unchanged); the household's mail says what the headline says and points to the dashboard, where A shows the list in its language. | A + ~1 h hub (one set + one test). Needs a hub release. | The household loses a list it could not read anyway; the operator keeps it. | | **C** | A + translated list in the mail: details carry `issue_keys` [{key,args}] and the hub renders them from its own bundle. | Two-repo, ~1 session; the hub must hold copies of controller keys (two producers, one sentence — the R-516 trap). | Drift between the two bundles. | **Pick: A now, B after the operator's word.** A is a small, decision-free fix in the shape R-516 item 10 already set. B is the cheapest honest mail; C buys a translated list at the price of a second copy of every sentence. **First slice (A), with its red test:** extend `r516_resource_msgs_test.go` with a case per remaining producer (`checkStoragePaths` with a fake unavailable / not-separate / >90 % path; the Docker and protected-container issues via their seams) asserting (1) the wire text is unchanged byte for byte and (2) the entry carries its key. Red-proof: drop one `MsgRef` → the case fails and the `internal/web` banner test shows the wire text. The i18n Go-parity gate (`scripts/i18n_go_parity.py`) checks the Hungarian values equal the frozen literals. ## One question for the operator **May the household's „system health" e-mail stop showing the raw technical note, and send the household to the dashboard for the list instead?** *If you do nothing:* A ships alone; the dashboard is right in both languages, and the mail keeps its note — a line of English inside curly brackets — as it does today.