5.1 KiB
REPORT — hub v0.70.1: the ghost customer's Delete button must exist
Date: 2026-07-22 · Repo: felhom.eu (hub only) · XS render-gate fix, DEPLOYED ·
STOP honoured: the ghost delete itself is NOT run — awaiting the operator's click.
The defect (fourth inert seam — a template gate this time)
v0.70.0's ghost-delete was fully implemented server-side (handleCustomerDelete/Preview accept
cfg == nil) and fully unreachable: the Danger-zone card — and the customerDeleteOpen/Submit
script — sat inside the {{if .HasConfig}} block that also wraps the RESET card
(customer_unified.html old ~L736–L945). A ghost rendered no Danger zone at all. Handler tests
POST directly and proved nothing about reachability. Confirmed live on demo-vm-felhom pre-fix.
The change
configs.gohandleCustomerUnified: newDeletableflag = the exact negation of the preview's 404 predicate (cfg == nil && no hosts && residue empty). Hosts were already fetched for the Host tab, so only the residue count is a new read — and it runs solely on the ghost shape. Lookup error → log +Deletable=false(fail toward hiding a destructive control).- Template: gate split — RESET card + its JS stay
{{if .HasConfig}}; Danger zone + delete dialog + delete JS gate on{{if .Deletable}}; Block/Unblock forms get an inner{{if .HasConfig}}; ghost shape gets a one-sentence intro prefix (the dialog explains the rest).
Tests (customer_ghost_delete_render_test.go) + red-proofs
Render tests against the real template set, real store on t.TempDir(), asserting the delete
form's action attribute and the customerDeleteOpen( call site (not free-text):
- A — ghost with residue (reports only, no config, 0 hosts): delete form + opener + script present; RESET form ABSENT; Block/Unblock ABSENT. PASS
- B — configured customer: every affordance of today present (RESET, Block, delete machinery);
plus the blocked→Unblock branch (fixture note:
SaveCustomerConfignever writes status — blocking goes throughSetCustomerConfigStatus, same as the /block handler). PASS - C — nothing left: page-level 404, mirroring the preview's semantics. There is no
renderable
Deletable=falsestate with real store reads —customer != nilrequires a report row (GetCustomerselects fromreports), and a report row makesresidue.Total() ≥ 1. The only false-branch shape short of that is a residue-lookup error (fail-safe, compile-trivial).
Red-proof 1 (RUN): Danger-zone gate reverted to {{if .HasConfig}} → test A FAILED on all
three delete-affordance assertions ("ghost page must render the delete form … dead UI"). Restored →
green.
Red-proof 2 (RUN): RESET card gated on Deletable (the lookalike) → the RESET-absent assertion
FAILED ("RESET card must NOT render for a ghost (found action=/configs/ghost/reset)").
Restored → green. git diff clean of scaffolding (0 REDPROOF markers).
Green gate: go build ./... && go vet ./... && go test ./... — green (web suite 12.0 s).
Ship (GitOps)
| Step | Evidence |
|---|---|
| Feature commit | f4c2c41 (code + template + tests + CHANGELOG + ROADMAP + CONTEXT + CLAUDE.md) |
| Image | felhom-hub:0.70.1 built + pushed, ID f1741b46bfbb |
| Manifest bump | separate chore commit 068bce6 (operator preference honoured) |
| Sync | ArgoCD hard-refresh + patch-sync → Synced/Healthy, rollout complete |
| Live verify | pod image felhom-hub:0.70.1, imageID digest sha256:f1741b46bfbb… (= the built image), clean startup log, /login 200. Footer is on password-gated pages — the visual check lands on the operator's next login (endpoint-level method stated per convention) |
AWAITING OPERATOR — the live leg is yours
Open demo-vm-felhom → Edit tab (Danger zone now renders with the ghost prefix) → Delete
customer… → three acks + typed id → confirm. Expected: customer gone from the Customers list,
journal legs residue=ok customer_delete=ok with skipped_no_config on the Hetzner/descriptor
legs, staleness emails stop. That click doubles as the v0.70.0 + v0.70.1 live validation; the
ROADMAP R-25b row flips its "PENDING" note once done.
Docs also in this train
hub/CHANGELOG.mdv0.70.1 (inert-seam framing, template/handler lines cited).CLAUDE.md: the seam-wiring rule now covers template gates — any conditional UI affordance ships with a render test per branch.- ROADMAP: R-25b annotated (v0.70.1 render fix, operator live leg PENDING); R-17 → CLOSED
(operator console action:
u629193-sub1deleted, no sub-accounts remain on PBS-storage-1, 2026-07-22).
Observations (recorded, not acted on)
- Pre-existing page-reachability quirk: a customer with host rows but neither config nor reports
404s on the unified page (configs.go
cfg == nil && customer == nil) while the delete preview would 200. No such state exists in practice (hosts imply enrollment implies reports); noted only. - The Create Config button for ghosts is untouched (re-adopting a ghost stays a legitimate path).