Files
felhom.eu/REPORT.md
T

5.1 KiB
Raw Blame History

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 ~L736L945). 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.go handleCustomerUnified: new Deletable flag = 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: SaveCustomerConfig never writes status — blocking goes through SetCustomerConfigStatus, same as the /block handler). PASS
  • C — nothing left: page-level 404, mirroring the preview's semantics. There is no renderable Deletable=false state with real store reads — customer != nil requires a report row (GetCustomer selects from reports), and a report row makes residue.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.md v0.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-sub1 deleted, 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).