v0.316.1: the asset tag actually reaches the dashboard pages
gates / gates (push) Failing after 48s

v0.316.0 shipped it dead. Every shell asked for `?v={{.AssetTag}}`, TestAssetTag_EveryShellUsesIt
passed, and the first live load through the public address rendered `/static/style.css?v=` —
EMPTY. addShellAssetData was called only by executeTemplateLang (the sign-in, claim, recovery,
guest-share and catch-all pages). The dashboard path had never needed it, because layout.html used
to ask for {{.Version}}, which baseData supplies; moving it to {{.AssetTag}} left nothing to
supply the value.

An empty buster is a CONSTANT. It would never have changed again — worse than the per-version one
it replaced, and invisible to a test that only checks the markup. executeTemplate now calls the
same function executeTemplateLang does, which is literally what the v0.255.0 comment above that
call asked for: "setting it at the single choke point every shell renders through means a new
shell cannot miss it". There were two choke points.

TestAssetTag_ReachesTheRenderedPage renders six real URLs through the production router and asserts
the buster is non-empty, 8 hex, and not a version. Red-proved by removing the call again: four
pages fail naming the empty value.

A PATCH VERSION, not a second push of 0.316.0 — the rule this release added, applied to its own
mistake rather than around it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
This commit is contained in:
2026-10-15 20:41:09 +02:00
parent 48d8674876
commit 006ceab58a
3 changed files with 64 additions and 0 deletions
+16
View File
@@ -1,3 +1,19 @@
## v0.316.1 — the asset tag actually reaches the dashboard pages (2026-10-15)
**MinAgent: 0.155.0** (unchanged). A patch release because **a version number is pushed once** — the
rule this release added, applied to its own mistake rather than around it.
- **v0.316.0 shipped the asset tag dead on every dashboard page.** Every shell asked for
`?v={{.AssetTag}}` and the template test passed, but `addShellAssetData` was only ever called by
`executeTemplateLang` — the stranger-facing pages. The dashboard path had never needed it, because
`layout.html` used to ask for `{{.Version}}`, which `baseData` supplies. The first live page load
rendered `/static/style.css?v=` — **empty, which is a constant, and a constant busts nothing.**
Found in a browser on the public address; the markup was right and the value was absent.
`executeTemplate` now calls the same one function, which is what v0.255.0's own comment asked for.
- `TestAssetTag_ReachesTheRenderedPage` asserts the rendered buster is NON-EMPTY, 8 hex and not a
version, on six real URLs through the production router. `TestAssetTag_EveryShellUsesIt` checks the
markup; it could not see this, and asserting a link exists proves nothing about what it carries.
## v0.316.0 — the app page as one column; a version number is pushed once (2026-10-15)
**MinAgent: 0.155.0** (unchanged — no new agent call). No golden (`09` §3 decision 250). Design:
+42
View File
@@ -1,6 +1,7 @@
package web
import (
"regexp"
"strings"
"testing"
)
@@ -64,6 +65,47 @@ func TestAssetTag_ChangesPerBuild(t *testing.T) {
}
}
// TestAssetTag_ReachesTheRenderedPage — the half TestAssetTag_EveryShellUsesIt cannot see.
//
// THIS TEST EXISTS BECAUSE THE SUITE MISSED A DEAD FIX. Every shell asked for
// `?v={{.AssetTag}}`, the template test passed, and the first live page load rendered
// `/static/style.css?v=` — empty. `addShellAssetData` was called only by executeTemplateLang (the
// stranger-facing pages); the dashboard path had never needed it, because layout.html used to ask
// for `{{.Version}}`, which baseData supplies. The markup was right and the value was absent, which
// is worse than the defect it replaced: an empty buster is a CONSTANT and never busts anything.
//
// Asserting the link EXISTS proves nothing. Assert it carries a value.
//
// RED-PROOF (REPORT.md): remove the addShellAssetData call from executeTemplate -> this fails with
// an empty tag on every dashboard page. Reverted.
func TestAssetTag_ReachesTheRenderedPage(t *testing.T) {
s := appPageHarness(t)
re := regexp.MustCompile(`/static/style\.css\?v=([^"']*)`)
for _, p := range []string{"/apps/adventurelog", "/apps/adventurelog/data", "/apps/grocy",
"/apps/grocy/install", "/stacks", "/dashboard"} {
rec := appGet(t, s, p)
if rec.Code != 200 {
continue // a page this harness cannot render is another test's problem
}
m := re.FindStringSubmatch(rec.Body.String())
if m == nil {
t.Errorf("%s: no stylesheet link at all", p)
continue
}
if m[1] == "" {
t.Errorf("%s: the stylesheet buster is EMPTY (%q) — an empty buster is a constant and "+
"never busts anything", p, m[0])
continue
}
if m[1] == "0.316.0" || strings.Contains(m[1], ".") {
t.Errorf("%s: the buster looks like a version (%q); it must be the per-build tag", p, m[1])
}
if len(m[1]) != 8 {
t.Errorf("%s: buster %q is %d chars, want the 8-hex tag", p, m[1], len(m[1]))
}
}
}
// TestAssetTag_EveryShellUsesIt — the fix is only a fix if the shells ask for it. Before v0.316.0
// two stranger-facing pages used the tag and the other seven shells used `?v={{.Version}}`, which
// is exactly the address that did not change between the three 0.315.0 builds.
+6
View File
@@ -1093,6 +1093,12 @@ func (s *Server) executeTemplate(w http.ResponseWriter, r *http.Request, name st
// fence for the guest share page, which has no admin session.
s.addEscrowBanner(data, r)
s.addMissedBackupBanner(data, r) // R-871 (decision 110)
// The stylesheet cache-buster, for the DASHBOARD shell. executeTemplateLang has called this for
// the stranger-facing pages since v0.255.0; this path did not need it while layout.html asked for
// `?v={{.Version}}`, which baseData supplies. v0.316.0 moved it to `{{.AssetTag}}` and the first
// live load rendered `?v=` — a constant, which is worse than the version it replaced. Both render
// paths now go through the one function, which is what v0.255.0's own comment asked for.
s.addShellAssetData(data)
lang := s.langFor(r)
s.addLanguageData(data, r, lang)
var buf bytes.Buffer