hub v0.70.1: the ghost customer's Delete button must exist (Danger-zone render gate split)

This commit is contained in:
2026-07-22 09:23:37 +02:00
parent 2cf37f263b
commit f4c2c4151b
7 changed files with 199 additions and 4 deletions
+30
View File
@@ -1,5 +1,35 @@
# Felhom Hub — Changelog
## v0.70.1 — the ghost customer's Delete button must exist (2026-07-22)
**The fourth inert-seam defect: v0.70.0's ghost-delete path was fully implemented and fully
unreachable.** `handleCustomerDelete`/`handleCustomerDeletePreview` accepted `cfg == nil`, the
ghost dialog branch existed in the template JS (`customer_unified.html` `if (d.has_config ===
false)`) — but the Danger-zone card containing the Delete button sat inside the `{{if .HasConfig}}`
block (old ~L736) that also wraps the RESET card. A ghost rendered no Danger zone → no button →
dead UI. Handler tests passed because they POST directly; nothing asserted the rendered page.
Observed live on `demo-vm-felhom` (2026-07-22): Edit tab showed Controller Update + Geo cards only.
- **Handler** (`configs.go` `handleCustomerUnified`): new `Deletable` page flag — the **exact
negation of the delete preview's 404 predicate** (`customer_delete.go`: `cfg == nil && no hosts
&& residue empty`), one truth, not a lookalike. Hosts were already fetched for the Host tab;
only the residue count is an extra read, and it runs solely on the ghost shape. A lookup error
logs and leaves `Deletable=false` — fail toward hiding a destructive control.
- **Template**: the old gate split. RESET card stays `{{if .HasConfig}}` (identity-preserving
re-onboarding — a ghost has no identity to preserve). Danger zone gates on `{{if .Deletable}}`;
inside it the Block/Unblock forms gain their own `{{if .HasConfig}}` (blocking gates dashboard
visibility of a configured customer — meaningless for a ghost). The `customerDeleteOpen/Submit`
script moved out with the card (it was inside the old gate). Ghost shape gets a one-sentence
intro prefix; the dialog already explains the rest.
- **Render tests** (`customer_ghost_delete_render_test.go`), asserting the delete form's `action`
and the `customerDeleteOpen(` call site: ghost-with-residue renders Delete only (no RESET, no
Block); configured customer keeps every affordance byte-for-byte (incl. the blocked→Unblock
branch); nothing-left 404s (there is no renderable `Deletable=false` state — `customer != nil`
implies a report row implies residue > 0). Two red-proofs run and recorded in `REPORT.md`.
- **The generalized seam-wiring rule now covers template gates:** any conditional UI affordance
ships with a render test per branch — handler tests that POST directly prove nothing about
reachability.
## v0.70.0 — a deleted customer actually disappears (the ghost + its alerts) (2026-07-21)
Found while validating v0.69.0 against the live hub, on the operator's report that `demo-vm-felhom`