From 9377c43a02cacb37c998bfbe2599c7afeb746692 Mon Sep 17 00:00:00 2001 From: Eugene Blikh Date: Sat, 8 Aug 2026 22:22:41 +0300 Subject: [PATCH] chrome: carry the service's extra hashed assets bench and cover each grew a pair of UplotCSSHref/UplotJSHref fields beside the embedded Page, compare a BundleHref, and all three wrote the same justification next to it: the hash in the name is a property of the binary rather than of a page, so a page handed its own asset URLs is one that can be written without them and silently render nothing where the chart was. That argument is about the chrome, so the slot belongs to the chrome. A map rather than named fields because ecore has no business knowing that this instance vendors uPlot; the service names its own artefacts and its layout reads them back with index, guarded on emptiness the way StyleHref is. --- README.md | 1 + chrome/chrome.go | 18 ++++++++++++++++++ chrome/chrome_test.go | 16 ++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/README.md b/README.md index 9a7e58a4a6affea1a3d9ade73227b4ae46555a22..63818376cf5085370d803206ebdf951e17d42b9f 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ copies of the same code. ```go svc := chrome.NewService(conf, "compare.sr.ht") svc.StyleHref = cssHref // after discovering the hashed stylesheet +svc.Assets = map[string]string{"bundle.js": bundleHref} // any further artefacts t := chrome.MustAttach(template.New("layout").Funcs(chrome.Funcs())) // ... parse the service's own templates into t ... diff --git a/chrome/chrome.go b/chrome/chrome.go index 7e6d1c6e5787c147ce70face07ac5b509dbaa55d..b02c8298c195e9c00009fb4f5b4fc77fc5e2ca60 100644 --- a/chrome/chrome.go +++ b/chrome/chrome.go @@ -135,6 +135,20 @@ type Page struct { StyleHref string // "" when the binary was built without a stylesheet + // Assets are the hashed hrefs of the extra build artefacts a layout links + // beyond the stylesheet — a vendored chart library, a front-end bundle — + // keyed by names the service picks. Read as {{index .Assets "uplot.js"}}, + // guarded on emptiness exactly like StyleHref. + // + // They belong to the chrome for the reason StyleHref does: the hash in the + // name is a property of this binary, not of any page. A page that had to + // be handed its own asset URLs is a page that can be written without them + // and silently render nothing where the chart was. + // + // The map is the Service's, shared by every Page it builds: written once + // at startup, read-only afterwards. A handler must not write to it. + Assets map[string]string + Environment string // uppercased; banner text ShowBanner bool // true outside production @@ -175,6 +189,9 @@ type Service struct { // ExtraNav holds service-specific switcher entries (e.g. a /tokens link), // rendered after the shared network entries, for authenticated viewers. ExtraNav []NavItem + // Assets holds the extra hashed asset hrefs every Page carries; see + // Page.Assets. Populate it at startup, next to StyleHref. + Assets map[string]string siteName string environment string @@ -250,6 +267,7 @@ func (s *Service) Page(r *http.Request, title, username string) Page { SelfOrigin: s.selfOrigin, HubOrigin: s.hubOrigin, StyleHref: s.StyleHref, + Assets: s.Assets, Environment: strings.ToUpper(s.environment), ShowBanner: s.environment != "" && s.environment != "production", ContainerClass: "container", diff --git a/chrome/chrome_test.go b/chrome/chrome_test.go index 10190ea7eea8a5ad9ddbb87c3d9800b952e58e24..ab5f5f7911302607cbbd9421ba529438f369ae54 100644 --- a/chrome/chrome_test.go +++ b/chrome/chrome_test.go @@ -201,6 +201,22 @@ func TestLoginURLForMatchesTheNav(t *testing.T) { "https%3A%2F%2Fcompare.example%2F~alice%2Fdemo%3Fa%3D1", svc.LoginURLFor(r)) } +// TestPageCarriesTheServiceAssets pins the slot three services grew their own +// copy of: the layout reads an extra hashed artefact off the chrome, not off +// the page's payload. +func TestPageCarriesTheServiceAssets(t *testing.T) { + svc := NewService(testConf(), "bench.sr.ht") + svc.Assets = map[string]string{"uplot.js": "/static/uplot.0badc0de.js"} + p := svc.Page(httptest.NewRequest("GET", "/", nil), "t", "alice") + + assert.Equal(t, "/static/uplot.0badc0de.js", p.Assets["uplot.js"]) + // A name the service never registered reads as empty, which is what the + // layout's {{if}} guard is written against. + assert.Empty(t, p.Assets["missing.js"]) + assert.Empty(t, NewService(testConf(), "bench.sr.ht"). + Page(httptest.NewRequest("GET", "/", nil), "t", "alice").Assets) +} + func TestServiceAccessors(t *testing.T) { svc := NewService(testConf(), "compare.sr.ht") assert.Equal(t, "https://compare.example", svc.SelfOrigin())