Serve Calendar theme from shared Estilo assets
9 files changed,
+106
−203
Makefile+5 −0go.mod+2 −1go.sum+5 −10internal/calendar/asset_theme.go+17 −0internal/calendar/render.go+1 −1internal/calendar/static/theme.css+0 −187internal/calendar/theme_test.go+52 −0internal/calendartest/vendored_test.go+15 −0internal/guardstest/guards_test.go+9 −4
CI passed — run details
diff --git a/Makefile b/Makefile| index 86ae38b..93e632b 100644 |
| --- a/Makefile |
| +++ b/Makefile |
| @@ -1,5 +1,10 @@ |
| APP := calendar |
| +private_comma := , |
| +export GOPRIVATE := $(if $(GOPRIVATE),$(GOPRIVATE)$(private_comma))amadan.net/oficina/estilo.git |
| +export GONOPROXY := $(if $(GONOPROXY),$(GONOPROXY)$(private_comma))amadan.net/oficina/estilo.git |
| +export GONOSUMDB := $(if $(GONOSUMDB),$(GONOSUMDB)$(private_comma))amadan.net/oficina/estilo.git |
| + |
| # What `carlos ship -target` defaults to, and therefore what a release |
| # must be built for. Building for your own machine and shipping that is a |
| # silent architecture mismatch: the binary uploads fine and fails to exec |
diff --git a/go.mod b/go.mod| index 776316c..81ffa62 100644 |
| --- a/go.mod |
| +++ b/go.mod |
| @@ -3,9 +3,11 @@ module amadan.net/oficina/calendar |
| go 1.25.0 |
| require ( |
| + amadan.net/oficina/estilo.git v0.0.0-20260908001419-672a447da335 |
| github.com/carlosframework/rastrillo v0.23.0 |
| github.com/chromedp/chromedp v0.14.2 |
| github.com/go-chi/chi/v5 v5.3.2 |
| + golang.org/x/crypto v0.55.0 |
| gorm.io/gorm v1.31.2 |
| ) |
| @@ -25,7 +27,6 @@ require ( |
| github.com/mattn/go-isatty v0.0.20 // indirect |
| github.com/ncruces/go-strftime v1.0.0 // indirect |
| github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect |
| - golang.org/x/crypto v0.55.0 // indirect |
| golang.org/x/sys v0.47.0 // indirect |
| golang.org/x/text v0.41.0 // indirect |
| gorm.io/plugin/dbresolver v1.6.2 // indirect |
diff --git a/go.sum b/go.sum| index 9e6296c..2c562f5 100644 |
| --- a/go.sum |
| +++ b/go.sum |
| @@ -1,3 +1,5 @@ |
| +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/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= |
| github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= |
| github.com/carlosframework/rastrillo v0.23.0 h1:tKAAlwPt4Pg/rkqk7o6jvV12bbZ0r34oEVpuOlKfX7w= |
| @@ -48,24 +50,17 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 |
| github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= |
| golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= |
| golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= |
| -golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= |
| -golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= |
| golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= |
| -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= |
| -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= |
| +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= |
| golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= |
| +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= |
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
| -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= |
| -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= |
| golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= |
| golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= |
| -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= |
| -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= |
| golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= |
| golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= |
| -golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= |
| -golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= |
| golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= |
| +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= |
| gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= |
| gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= |
| gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= |
diff --git a/internal/calendar/asset_theme.go b/internal/calendar/asset_theme.go| new file mode 100644 |
| index 0000000..91a9ef9 |
| --- /dev/null |
| +++ b/internal/calendar/asset_theme.go |
| @@ -0,0 +1,17 @@ |
| +package calendar |
| + |
| +import ( |
| + "io/fs" |
| + "testing/fstest" |
| + |
| + "amadan.net/oficina/estilo.git/theme" |
| +) |
| + |
| +type sharedThemeFS struct{ fs.FS } |
| + |
| +func (f sharedThemeFS) Open(name string) (fs.File, error) { |
| + if name == "static/theme.css" { |
| + return fstest.MapFS{name: &fstest.MapFile{Data: theme.CSS(), Mode: 0444}}.Open(name) |
| + } |
| + return f.FS.Open(name) |
| +} |
diff --git a/internal/calendar/render.go b/internal/calendar/render.go| index 2eab125..df528e6 100644 |
| --- a/internal/calendar/render.go |
| +++ b/internal/calendar/render.go |
| @@ -24,7 +24,7 @@ var appFS embed.FS |
| // assets fingerprints static/: {{asset "static/tokens.css"}} in a |
| // template returns a content-hashed URL, served immutable-cacheable by |
| // the handler New mounts. |
| -var assets = rastrillo.NewAssets(appFS) |
| +var assets = rastrillo.NewAssets(sharedThemeFS{appFS}) |
| // pageNames is every page this app renders. Kept as a list rather than |
| // discovered from the filesystem so a template that is never rendered |
diff --git a/internal/calendar/static/theme.css b/internal/calendar/static/theme.css| deleted file mode 100644 |
| index 5da4384..0000000 |
| --- a/internal/calendar/static/theme.css |
| +++ /dev/null |
| @@ -1,187 +0,0 @@ |
| -/* day — rastrillo's default theme: an everyday blue on white and grey. |
| - Structure lives in tokens.css; this file is colour, type and shape. |
| - rastrillo new writes it once as static/theme.css. |
| - |
| - The look: the ordinary, personable interface. A white page, a plain |
| - grey text scale, one friendly blue that means "this does something", |
| - and the conventional green/amber/red for status. Nothing here is |
| - trying to be memorable — day is the theme an app ships on while it |
| - works out what it wants to look like, and the one that never gets in |
| - the way of the content. Semantic status colours are functional and |
| - never the accent, so a pill can never be mistaken for a link. |
| - |
| - ── One block, both schemes ─────────────────────────────────────────── |
| - Every token is declared once, as light-dark(<light>, <dark>), under a |
| - single :root that sets color-scheme: light dark. The browser picks the |
| - half that matches the resolved scheme, so there is no second copy of |
| - the palette to keep in sync and no way to author dark half-done. The |
| - two rules at the bottom are the whole toggle: setting color-scheme on |
| - :root[data-theme=…] re-resolves every light-dark() in the file at |
| - once, in both directions, which is why an explicit choice beats the OS |
| - without restating a single colour. |
| - |
| - Tokens with one value in both schemes — the font stack, the radii — |
| - are written plain, because light-dark(x, x) says nothing twice. |
| - |
| - light-dark() is a colour function: it may only stand where a <color> |
| - is expected. So a shadow token wraps its colour in it and writes the |
| - geometry once, outside the call — `0 8px 24px light-dark(a, b)`, never |
| - `light-dark(0 8px 24px a, …)`, which no browser would accept as a |
| - box-shadow. |
| - |
| - ── The shape axis ──────────────────────────────────────────────────── |
| - Radius and shadow are the theme's now, not tokens.css's: a theme that |
| - can only change colour cannot change how an app feels. day is soft and |
| - unremarkable — 8px cards, 6px controls, shadows with a wide blur and |
| - little alpha, the depth of paper resting on paper. |
| - |
| - ── WCAG 2.2 AA, measured ───────────────────────────────────────────── |
| - Normal text needs 4.5:1; large text and UI component boundaries need |
| - 3:1. Every pair below was computed from these exact hex values, in |
| - both schemes. If you change a token, recompute its rows. |
| - |
| - LIGHT ratio requirement |
| - --rst-text on --rst-surface 16.91 4.5:1 text |
| - --rst-text on --rst-bg 16.91 4.5:1 text |
| - --rst-text on --rst-surface-2 15.77 4.5:1 text |
| - --rst-text on --rst-accent-soft 15.21 4.5:1 text |
| - --rst-text-muted on --rst-surface 7.56 4.5:1 text |
| - --rst-text-muted on --rst-bg 7.56 4.5:1 text |
| - --rst-text-muted on --rst-surface-2 7.05 4.5:1 text |
| - --rst-text-muted on --rst-accent-soft 6.80 4.5:1 text |
| - --rst-text-faint on --rst-surface 5.29 4.5:1 text |
| - --rst-text-faint on --rst-bg 5.29 4.5:1 text |
| - --rst-text-faint on --rst-surface-2 4.93 4.5:1 text |
| - --rst-text-faint on --rst-accent-soft 4.76 4.5:1 text |
| - --rst-accent on --rst-surface 5.28 4.5:1 text + focus ring |
| - --rst-accent on --rst-bg 5.28 4.5:1 text + focus ring |
| - --rst-accent on --rst-surface-2 4.93 4.5:1 text |
| - --rst-accent on --rst-accent-soft 4.75 4.5:1 text |
| - --rst-on-accent on --rst-accent 5.28 4.5:1 button label |
| - --rst-on-accent on --rst-accent-strong 7.18 4.5:1 button label, hover |
| - --rst-line-strong on --rst-surface 3.63 3:1 control border |
| - --rst-line-strong on --rst-bg 3.63 3:1 control border |
| - --rst-line-strong on --rst-surface-2 3.39 3:1 control border |
| - pill neutral fg on its own tint 9.02 4.5:1 text |
| - pill positive fg on its own tint 6.49 4.5:1 text |
| - pill warning fg on its own tint 6.15 4.5:1 text |
| - pill negative fg on its own tint 5.30 4.5:1 text |
| - --rst-on-accent on negative fg (fill) 6.47 4.5:1 danger button label |
| - |
| - DARK ratio requirement |
| - --rst-text on --rst-surface 13.74 4.5:1 text |
| - --rst-text on --rst-bg 15.32 4.5:1 text |
| - --rst-text on --rst-surface-2 14.63 4.5:1 text |
| - --rst-text on --rst-accent-soft 13.02 4.5:1 text |
| - --rst-text-muted on --rst-surface 7.56 4.5:1 text |
| - --rst-text-muted on --rst-bg 8.43 4.5:1 text |
| - --rst-text-muted on --rst-surface-2 8.05 4.5:1 text |
| - --rst-text-muted on --rst-accent-soft 7.16 4.5:1 text |
| - --rst-text-faint on --rst-surface 6.33 4.5:1 text |
| - --rst-text-faint on --rst-bg 7.06 4.5:1 text |
| - --rst-text-faint on --rst-surface-2 6.74 4.5:1 text |
| - --rst-text-faint on --rst-accent-soft 6.00 4.5:1 text |
| - --rst-accent on --rst-surface 6.95 4.5:1 text + focus ring |
| - --rst-accent on --rst-bg 7.75 4.5:1 text + focus ring |
| - --rst-accent on --rst-surface-2 7.40 4.5:1 text |
| - --rst-accent on --rst-accent-soft 6.59 4.5:1 text |
| - --rst-on-accent on --rst-accent 7.86 4.5:1 button label |
| - --rst-on-accent on --rst-accent-strong 10.29 4.5:1 button label, hover |
| - --rst-line-strong on --rst-surface 4.26 3:1 control border |
| - --rst-line-strong on --rst-bg 4.75 3:1 control border |
| - --rst-line-strong on --rst-surface-2 4.53 3:1 control border |
| - pill neutral fg on its own tint 8.44 4.5:1 text |
| - pill positive fg on its own tint 10.18 4.5:1 text |
| - pill warning fg on its own tint 9.62 4.5:1 text |
| - pill negative fg on its own tint 7.85 4.5:1 text |
| - --rst-on-accent on negative fg (fill) 8.99 4.5:1 danger button label |
| - |
| - The tightest pair in the set is --rst-accent on --rst-accent-soft in |
| - light, at 4.75:1 against a 4.5:1 floor. Put a token on a surface not |
| - listed above and you are guessing; recompute first. |
| - |
| - --rst-line is a hairline divider between rows, not a control border or |
| - a meaningful graphic, so 1.4.11's 3:1 does not apply to it. Control |
| - borders use --rst-line-strong, which does clear 3:1. |
| - |
| - --rst-header-rule is not in the table either, and deliberately so |
| - (design doc §6-v2.2): the page header's rule is decoration, not |
| - structure — the heading's size, weight and spacing carry the |
| - structure — so no contrast floor applies to it and it is not in |
| - ui/contrast_test.go's pair table. Do not argue it up to 3:1. |
| - |
| - Pagination chips are the one deliberate exception: they are bordered |
| - with --rst-line rather than --rst-line-strong, because a page strip |
| - ringed in hard outlines shouts louder than the list it belongs to. |
| - 1.4.11 is satisfied without that border — each chip's meaning is |
| - carried by its text label at 4.5:1 or better, and the current page |
| - adds a filled background, a heavier weight and aria-current on top. |
| - |
| - --rst-shadow-pop, --rst-shadow-knob, --rst-shadow-lift and |
| - --rst-overlay are not in the table: they paint a translucent shadow or |
| - scrim over whatever sits underneath, not text over a fixed |
| - background, so a contrast ratio does not apply. The scrim deepens in |
| - dark, matching --rst-shadow-pop's own jump: black reads lighter |
| - against a dark backdrop, so the overlay needs more alpha to still |
| - read as a dimming layer. |
| - ──────────────────────────────────────────────────────────────────── */ |
| - |
| -:root { |
| - color-scheme: light dark; |
| - |
| - /* Type: the family only. Sizes and spacing are structural and stay in |
| - tokens.css. */ |
| - --rst-font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; |
| - |
| - /* Shape: soft, ordinary, unremarkable. */ |
| - --rst-radius: 8px; |
| - --rst-radius-sm: 6px; |
| - --rst-radius-pill: 999px; |
| - |
| - /* Surfaces and lines */ |
| - --rst-bg: light-dark(#ffffff, #111418); |
| - --rst-surface: light-dark(#ffffff, #1a1f26); |
| - --rst-surface-2: light-dark(#f6f7f8, #15191f); |
| - --rst-line: light-dark(#e5e7eb, #2a3038); |
| - --rst-line-strong: light-dark(#7f8792, #79828e); |
| - |
| - /* The page header's rule, derived rather than authored: the accent |
| - folded into the line at 18%, so a theme that changes its accent |
| - gets a matching rule with no second value to keep in step. day's |
| - is barely there — a hairline you would call grey until you put it |
| - beside plain's. Decorative: it carries no contrast floor. */ |
| - --rst-header-rule: color-mix(in oklab, var(--rst-accent) 18%, var(--rst-line)); |
| - |
| - /* Text */ |
| - --rst-text: light-dark(#1a1d21, #e8eaed); |
| - --rst-text-muted: light-dark(#4b5563, #a8b0ba); |
| - --rst-text-faint: light-dark(#656c78, #98a1ac); |
| - |
| - /* Accent */ |
| - --rst-accent: light-dark(#2464e0, #7ba7ff); |
| - --rst-accent-strong: light-dark(#1a4fc0, #a3c0ff); |
| - --rst-accent-soft: light-dark(#eef3fe, #17233a); |
| - --rst-on-accent: light-dark(#ffffff, #0b1220); |
| - |
| - /* Status tones */ |
| - --rst-tone-neutral-fg: light-dark(#374151, #c3c9d1); |
| - --rst-tone-neutral-bg: light-dark(#eef0f2, #262c34); |
| - --rst-tone-positive-fg: light-dark(#166534, #6ee7a8); |
| - --rst-tone-positive-bg: light-dark(#dcfce7, #10281c); |
| - --rst-tone-warning-fg: light-dark(#854d0e, #f0c46a); |
| - --rst-tone-warning-bg: light-dark(#fef3c7, #2c2110); |
| - --rst-tone-negative-fg: light-dark(#b91c1c, #f79aa0); |
| - --rst-tone-negative-bg: light-dark(#fee2e2, #331819); |
| - |
| - /* Depth */ |
| - --rst-shadow-pop: 0 8px 24px light-dark(rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.5)); |
| - --rst-shadow-knob: 0 1px 2px rgba(0, 0, 0, 0.3); |
| - --rst-shadow-lift: 0 1px 2px rgba(0, 0, 0, 0.08); |
| - --rst-overlay: light-dark(rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0.6)); |
| -} |
| - |
| -/* The whole toggle. Setting color-scheme re-resolves every light-dark() |
| - above, so an explicit choice beats the OS in both directions without |
| - restating a colour. */ |
| -:root[data-theme="light"] { color-scheme: light; } |
| -:root[data-theme="dark"] { color-scheme: dark; } |
diff --git a/internal/calendar/theme_test.go b/internal/calendar/theme_test.go| new file mode 100644 |
| index 0000000..08bd7e9 |
| --- /dev/null |
| +++ b/internal/calendar/theme_test.go |
| @@ -0,0 +1,52 @@ |
| +package calendar |
| + |
| +import ( |
| + "bytes" |
| + "net/http" |
| + "net/http/httptest" |
| + "path/filepath" |
| + "regexp" |
| + "strings" |
| + "testing" |
| + |
| + "amadan.net/oficina/estilo.git/theme" |
| +) |
| + |
| +func TestSharedThemeThroughRenderedAssetRoute(t *testing.T) { |
| + a, err := New(Config{DBPath: filepath.Join(t.TempDir(), "calendar.db"), Env: "development"}) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + t.Cleanup(func() { a.Close() }) |
| + get := func(path string) *httptest.ResponseRecorder { |
| + w := httptest.NewRecorder() |
| + a.Handler().ServeHTTP(w, httptest.NewRequest(http.MethodGet, "http://localhost:8080"+path, nil)) |
| + return w |
| + } |
| + page := get("/signin") |
| + if page.Code != http.StatusOK { |
| + t.Fatalf("signin status %d", page.Code) |
| + } |
| + match := regexp.MustCompile(`/static/theme\.[0-9a-f]{16}\.css`).FindString(page.Body.String()) |
| + if match == "" { |
| + t.Fatal("rendered page lacks a fingerprinted theme") |
| + } |
| + for _, tc := range []struct { |
| + path string |
| + cache string |
| + }{{match, "public, max-age=31536000, immutable"}, {"/static/theme.css", "no-cache"}, {"/static/theme.0000000000000000.css", "no-cache"}} { |
| + w := get(tc.path) |
| + if w.Code != http.StatusOK || !bytes.Equal(w.Body.Bytes(), theme.CSS()) { |
| + t.Fatalf("theme route %s did not serve shared bytes: %d", tc.path, w.Code) |
| + } |
| + if !strings.HasPrefix(w.Header().Get("Content-Type"), "text/css") { |
| + t.Fatal("theme has wrong content type") |
| + } |
| + if w.Header().Get("Cache-Control") != tc.cache { |
| + t.Fatal("theme cache policy does not match fingerprint") |
| + } |
| + } |
| + if !bytes.Contains(theme.CSS(), []byte("--rst-header-rule:")) { |
| + t.Fatal("Calendar header token disappeared") |
| + } |
| +} |
diff --git a/internal/calendartest/vendored_test.go b/internal/calendartest/vendored_test.go| index ef5dc34..a232932 100644 |
| --- a/internal/calendartest/vendored_test.go |
| +++ b/internal/calendartest/vendored_test.go |
| @@ -6,6 +6,7 @@ import ( |
| "path/filepath" |
| "testing" |
| + "amadan.net/oficina/estilo.git/theme" |
| "github.com/carlosframework/rastrillo/ui" |
| ) |
| @@ -39,6 +40,20 @@ func TestVendoredAssetsMatchTheLibrary(t *testing.T) { |
| t.Fatalf("unknown theme %q", vendoredTheme) |
| } |
| for name, lib := range assets { |
| + if name == "theme.css" { |
| + 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(lib)) { |
| + t.Fatal("shared theme differs from the selected framework day theme") |
| + } |
| + continue |
| + } |
| if vendoredIsMine[name] { |
| continue |
| } |
diff --git a/internal/guardstest/guards_test.go b/internal/guardstest/guards_test.go| index f4d37d5..601eb1f 100644 |
| --- a/internal/guardstest/guards_test.go |
| +++ b/internal/guardstest/guards_test.go |
| @@ -18,6 +18,8 @@ import ( |
| "runtime" |
| "strings" |
| "testing" |
| + |
| + "amadan.net/oficina/estilo.git/theme" |
| ) |
| // repoRoot is found from this file's own path, never from the working |
| @@ -75,7 +77,6 @@ var lineCapExempt = map[string]string{ |
| "internal/calendar/static/rastrillo.js": "vendored from rastrillo; pinned byte-for-byte", |
| "internal/calendar/static/select.js": "vendored from rastrillo; pinned byte-for-byte", |
| "internal/calendar/static/tokens.css": "vendored from rastrillo; pinned byte-for-byte", |
| - "internal/calendar/static/theme.css": "vendored from rastrillo; pinned byte-for-byte", |
| } |
| func TestNoFileExceedsTheLineCap(t *testing.T) { |
| @@ -183,9 +184,13 @@ func TestEveryTokenWeUseIsDefined(t *testing.T) { |
| declared := map[string]bool{} |
| for _, name := range []string{"tokens.css", "theme.css"} { |
| - b, err := os.ReadFile(filepath.Join(static, name)) |
| - if err != nil { |
| - t.Fatalf("read %s: %v", name, err) |
| + b := theme.CSS() |
| + if name != "theme.css" { |
| + var err error |
| + b, err = os.ReadFile(filepath.Join(static, name)) |
| + if err != nil { |
| + t.Fatalf("read %s: %v", name, err) |
| + } |
| } |
| for _, m := range regexp.MustCompile(`(--rst-[A-Za-z0-9-]+)\s*:`).FindAllStringSubmatch(string(b), -1) { |
| declared[m[1]] = true |