Serve the shared Estilo day theme through existing assets
4 files changed,
+80
−8
go.mod+2 −2go.sum+4 −0internal/docs/render.go+6 −6internal/docs/theme_assets_test.go+68 −0
CI passed — run details
diff --git a/go.mod b/go.mod| index 11b8326..4d87a39 100644 |
| --- a/go.mod |
| +++ b/go.mod |
| @@ -3,8 +3,8 @@ module github.com/carlosframework/docs |
| go 1.26.5 |
| require ( |
| - amadan.net/oficina/estilo.git v0.0.0-20260907201639-532746acdbc1 |
| - github.com/carlosframework/rastrillo v0.22.0 |
| + amadan.net/oficina/estilo.git v0.0.0-20260908001419-672a447da335 |
| + github.com/carlosframework/rastrillo v0.23.0 |
| github.com/go-chi/chi/v5 v5.3.2 |
| github.com/google/uuid v1.6.0 |
| github.com/tetratelabs/wazero v1.12.0 |
diff --git a/go.sum b/go.sum| index 28bd468..c4c7b70 100644 |
| --- a/go.sum |
| +++ b/go.sum |
| @@ -1,7 +1,11 @@ |
| amadan.net/oficina/estilo.git v0.0.0-20260907201639-532746acdbc1 h1:9Qrzr6xT0QuaTAK4Bq3M8nWDCgkBmXy7H9BR3C+DZOo= |
| amadan.net/oficina/estilo.git v0.0.0-20260907201639-532746acdbc1/go.mod h1:AZvUebwQkfBh2d0TIOAO/AViIIsRx774jL7ic8XOckg= |
| +amadan.net/oficina/estilo.git v0.0.0-20260908001419-672a447da335 h1:YogZKYINrPNXRfPedXxOIDbGCMY4ff7yAdohYphEWMg= |
| +amadan.net/oficina/estilo.git v0.0.0-20260908001419-672a447da335/go.mod h1:0R45/vCUAT+kpS7S6K/ZxBdE8f7YxFN4HT3HyDJ3nPY= |
| github.com/carlosframework/rastrillo v0.22.0 h1:tG02WAiy2VSYqmLT6kRfYbUaiJd/pBhTIWxxjjsEoI8= |
| github.com/carlosframework/rastrillo v0.22.0/go.mod h1:pZlrE5F5OhspvZFzef0DrfjHAjA8FXycb9J5/8vc8Tw= |
| +github.com/carlosframework/rastrillo v0.23.0 h1:tKAAlwPt4Pg/rkqk7o6jvV12bbZ0r34oEVpuOlKfX7w= |
| +github.com/carlosframework/rastrillo v0.23.0/go.mod h1:pZlrE5F5OhspvZFzef0DrfjHAjA8FXycb9J5/8vc8Tw= |
| github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327 h1:UQ4AU+BGti3Sy/aLU8KVseYKNALcX9UXY6DfpwQ6J8E= |
| github.com/chromedp/cdproto v0.0.0-20250724212937-08a3db8b4327/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= |
| github.com/chromedp/chromedp v0.14.2 h1:r3b/WtwM50RsBZHMUm9fsNhhzRStTHrKdr2zmwbZSzM= |
diff --git a/internal/docs/render.go b/internal/docs/render.go| index 9edb41b..5011faa 100644 |
| --- a/internal/docs/render.go |
| +++ b/internal/docs/render.go |
| @@ -10,6 +10,7 @@ import ( |
| "testing/fstest" |
| "amadan.net/oficina/estilo.git/savestatus" |
| + "amadan.net/oficina/estilo.git/theme" |
| "github.com/carlosframework/rastrillo" |
| "github.com/carlosframework/rastrillo/ui" |
| ) |
| @@ -555,10 +556,9 @@ func (rd *renderer) page(w http.ResponseWriter, r *http.Request, name string, da |
| } |
| // vendoredStaticFS builds the static/ tree rastrillo.Assets serves — |
| -// tokens.css and the day theme via ui.TokensCSS()/ui.ThemeCSS("day") |
| -// as the brief requires, plus the three scripts the shell's <head> |
| -// links unconditionally (ui.ShimJS/SelectJS/DatetimeJS) — as bytes |
| -// taken straight from the rastrillo module. Nothing here is copied to |
| +// the shared Estilo day theme, plus tokens.css and the three scripts |
| +// the shell's <head> links unconditionally (ui.ShimJS/SelectJS/DatetimeJS) |
| +// taken straight from the selected rastrillo module. Nothing here is copied to |
| // static/ by hand: rastrillo new would normally vendor these to disk |
| // once at scaffold time (ui.VendoredAssets's own doc comment), but |
| // this app was hand-built rather than scaffolded, so it takes the |
| @@ -566,7 +566,7 @@ func (rd *renderer) page(w http.ResponseWriter, r *http.Request, name string, da |
| // testing/fstest.MapFS is the standard library's only in-memory |
| // fs.FS — used here as a data structure, not as a test helper. |
| func vendoredStaticFS() (fs.FS, error) { |
| - theme, ok := ui.ThemeCSS(dayTheme) |
| + _, ok := ui.ThemeCSS(dayTheme) |
| if !ok { |
| return nil, fmt.Errorf("docs: rastrillo ui: theme %q not shipped", dayTheme) |
| } |
| @@ -574,7 +574,7 @@ func vendoredStaticFS() (fs.FS, error) { |
| "static/estilo-save-status.css": {Data: savestatus.CSS()}, |
| "static/estilo-save-status.mjs": {Data: savestatus.JS()}, |
| "static/tokens.css": {Data: ui.TokensCSS()}, |
| - "static/theme.css": {Data: theme}, |
| + "static/theme.css": {Data: theme.CSS()}, |
| "static/rastrillo.js": {Data: ui.ShimJS()}, |
| "static/select.js": {Data: ui.SelectJS()}, |
| "static/datetime.js": {Data: ui.DatetimeJS()}, |
diff --git a/internal/docs/theme_assets_test.go b/internal/docs/theme_assets_test.go| new file mode 100644 |
| index 0000000..6a0b61b |
| --- /dev/null |
| +++ b/internal/docs/theme_assets_test.go |
| @@ -0,0 +1,68 @@ |
| +package docs |
| + |
| +import ( |
| + "bytes" |
| + "crypto/sha256" |
| + "fmt" |
| + "net/http" |
| + "net/http/httptest" |
| + "strings" |
| + "testing" |
| + |
| + "amadan.net/oficina/estilo.git/theme" |
| + "github.com/carlosframework/rastrillo/ui" |
| +) |
| + |
| +func TestSharedThemeAssetFingerprintAndCache(t *testing.T) { |
| + app, err := NewForTest(t) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + page := serve(app, httptest.NewRequest(http.MethodGet, "/signin", nil)) |
| + if page.Code != http.StatusOK { |
| + t.Fatalf("signin status %d", page.Code) |
| + } |
| + day, ok := ui.ThemeCSS(dayTheme) |
| + if !ok { |
| + t.Fatal("selected day missing") |
| + } |
| + body := func(css []byte) []byte { |
| + if bytes.HasPrefix(css, []byte("/*")) { |
| + if end := bytes.Index(css, []byte("*/")); end >= 0 { |
| + return css[end+2:] |
| + } |
| + } |
| + return css |
| + } |
| + if !bytes.Equal(body(theme.CSS()), body(day)) { |
| + t.Fatal("shared theme differs from selected day") |
| + } |
| + for _, item := range []struct { |
| + name string |
| + data []byte |
| + }{ |
| + {"theme", theme.CSS()}, {"tokens", ui.TokensCSS()}, |
| + } { |
| + sum := sha256.Sum256(item.data) |
| + hashed := fmt.Sprintf("/static/%s.%x.css", item.name, sum[:8]) |
| + if !strings.Contains(page.Body.String(), `href="`+hashed+`"`) { |
| + t.Fatalf("missing selected %s fingerprint", item.name) |
| + } |
| + for _, path := range []string{hashed, "/static/" + item.name + ".css", "/static/" + item.name + ".0000000000000000.css"} { |
| + res := serve(app, httptest.NewRequest(http.MethodGet, path, nil)) |
| + if res.Code != http.StatusOK || !bytes.Equal(res.Body.Bytes(), item.data) { |
| + t.Fatalf("%s asset response mismatch status%d", item.name, res.Code) |
| + } |
| + if !strings.HasPrefix(res.Header().Get("Content-Type"), "text/css") { |
| + t.Fatal("asset MIME mismatch") |
| + } |
| + want := "no-cache" |
| + if path == hashed { |
| + want = "public, max-age=31536000, immutable" |
| + } |
| + if res.Header().Get("Cache-Control") != want { |
| + t.Fatal("asset cache policy mismatch") |
| + } |
| + } |
| + } |
| +} |