vitrine

theming: append the :root override, don't prepend -- cascade order matters

commit 46fd58b7d44a19fdecb7e51db1014d99da995085 · parent 8921cae64f · browse this tree
Ximianth, 2026-07-30 11:10:44 UTC

Caught live in a browser after deploy: the prepended theme block lost to the
embedded stylesheet's own :root --accent, because at equal specificity the
LATER declaration wins.  The stylesheet test asserted presence and position
but position was asserted the wrong way around; it now requires the override
at the end, and was watched failing against the prepend order.
diff --git a/serve.go b/serve.go
index b464ed0..01bd142 100644
--- a/serve.go
+++ b/serve.go
@@ -107,10 +107,11 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	// keeping it distinct from any configured mount name.
 	if r.URL.Path == "/-/style.css" {
 		w.Header().Set("Content-Type", "text/css; charset=utf-8")
-		// The theme's :root block precedes the embedded stylesheet,
-		// so its --accent wins over the stylesheet's default by cascade order.
-		w.Write(s.theme.CSS)
+		// The theme's :root block follows the embedded stylesheet:
+		// at equal specificity the later declaration wins,
+		// so appending is what lets the theme's --accent override the stylesheet's default.
 		w.Write(styleCSS)
+		w.Write(s.theme.CSS)
 		return
 	}
 
diff --git a/theme.go b/theme.go
index abb3c74..ccf8209 100644
--- a/theme.go
+++ b/theme.go
@@ -15,7 +15,7 @@ import (
 // theme is one host's visual identity, computed once at startup.
 type theme struct {
 	Name string // the seed itself, shown in the nav next to the site name
-	CSS  []byte // ":root" accent override, prepended to the stylesheet by the /-/style.css handler
+	CSS  []byte // ":root" accent override, appended after the stylesheet by the /-/style.css handler
 }
 
 func newTheme(seed string) theme {
diff --git a/theme_test.go b/theme_test.go
index 571ee0e..5a9fc7a 100644
--- a/theme_test.go
+++ b/theme_test.go
@@ -57,8 +57,10 @@ func TestHostLabel(t *testing.T) {
 	}
 }
 
-// TestStylesheetCarriesThemeAccent checks that /-/style.css leads with the
-// computed :root override and still contains the embedded stylesheet after it.
+// TestStylesheetCarriesThemeAccent checks that /-/style.css ends with the
+// computed :root override, after the embedded stylesheet:
+// at equal specificity the later declaration wins,
+// so appending (not prepending) is what makes the theme accent take effect.
 func TestStylesheetCarriesThemeAccent(t *testing.T) {
 	rec := get(t, newTestServer(t), "/-/style.css")
 	if rec.Code != http.StatusOK {
@@ -67,11 +69,11 @@ func TestStylesheetCarriesThemeAccent(t *testing.T) {
 	body := rec.Body.String()
 	// "testhost" is the seed newTestServer uses; it hashes to hue 351, lightness 35%.
 	want := ":root { --accent: hsl(351 70% 35%); }\n"
-	if !strings.HasPrefix(body, want) {
-		t.Errorf("stylesheet does not start with theme override %q; head: %q", want, body[:min(len(body), 120)])
+	if !strings.HasSuffix(body, want) {
+		t.Errorf("stylesheet does not end with theme override %q; tail: %q", want, body[max(0, len(body)-120):])
 	}
 	if !strings.Contains(body, "--paper") {
-		t.Errorf("embedded stylesheet missing after theme override")
+		t.Errorf("embedded stylesheet missing before theme override")
 	}
 }