fix: lifecycle methods need value receivers - app detail page 500 (v0.158.1)

Shipped in v0.158.0, caught live within the hour. /apps/<slug> returned 500
for EVERY app: html/template cannot call a pointer-receiver method on a
non-addressable value, and appDetailHandler passes Meta as a VALUE inside a
map[string]interface{}.

It compiled and every test passed because nothing rendered app_info - the
catalog tests used the funcmap route, which takes a value and works either
way. A template method call is only checked when the template runs.

Adds TestAppInfoRendersForEveryLifecycle with the handler's exact data shape.
This commit is contained in:
2026-07-21 16:33:00 +02:00
parent 5fdd2039fd
commit 0fbd272ad2
3 changed files with 95 additions and 3 deletions
+27
View File
@@ -1,5 +1,32 @@
## Changelog
### v0.158.1 — fix: the lifecycle methods broke every app detail page (2026-07-21)
**Defect shipped in v0.158.0 and caught live within the hour. `/apps/<slug>` returned HTTP 500 for
EVERY app**, not just withdrawn ones.
`EffectiveLifecycle` / `CanInstall` / `IsAbandoned` were declared with POINTER receivers.
`appDetailHandler` puts `data["Meta"] = found.Meta` — a `stacks.Metadata` VALUE inside a
`map[string]interface{}` — and html/template cannot call a pointer-receiver method on a
non-addressable value. So `{{if .Meta.IsAbandoned}}` failed at RENDER time:
```
executing "app_info" at <.Meta.IsAbandoned>: can't evaluate field IsAbandoned in type interface {}
```
Switched to value receivers, with the reason recorded at the declaration so it is not "tidied" back.
**Why the tests missed it, which is the more useful lesson:** it compiles, `go vet` is silent, and
every v0.158.0 test passed — because none of them rendered `app_info`. The catalog-page tests
exercised the funcmap route (`lifecycleBadge .Meta`), which takes a value and works either way. A
template method call is only ever checked when the template actually runs.
Added `TestAppInfoRendersForEveryLifecycle`, which renders the real `app_info` template through the
production tree with the handler's exact data shape — `"Meta"` as a VALUE in a
`map[string]interface{}`, deliberately not a pointer, because the pointer is what hides the bug.
Red-proof: restoring the pointer receiver reproduces the 500 for every lifecycle value including the
empty one.
### v0.158.0 — apps get a lifecycle: available / hidden / abandoned (2026-07-21)
No agent coupling; MinAgent unchanged.