web: scope each syntax palette to its color scheme !88

merged merged by cmc on 2026-08-26 23:02 UTC · krz/gitbay:chroma-dark into main

2 files changed, +21 −5

Layout: unified · split

e2e/diffweb_test.go +13 −1
@@ -88,9 +88,21 @@ func TestDiffRendering(t *testing.T) {
88 if !strings.Contains(body, `<td class="src chroma">`) { 88 if !strings.Contains(body, `<td class="src chroma">`) {
89 t.Error("diff cell is not a chroma wrapper, so token classes go unstyled") 89 t.Error("diff cell is not a chroma wrapper, so token classes go unstyled")
90 } 90 }
91 if !strings.Contains(stylesheet(t, inst), ".chroma .k") { 91 css := stylesheet(t, inst)
92 if !strings.Contains(css, ".chroma .k") {
92 t.Error("stylesheet has no chroma token rules to match") 93 t.Error("stylesheet has no chroma token rules to match")
93 } 94 }
95 // Each palette is scoped to its own scheme. Unscoped, the light one
96 // leaks into dark mode for every token the dark palette does not name.
97 if !strings.Contains(css, "@media (prefers-color-scheme: light)") ||
98 !strings.Contains(css, "@media (prefers-color-scheme: dark)") {
99 t.Error("chroma palettes are not both scheme-scoped")
100 }
101 if i := strings.Index(css, ".chroma .na"); i >= 0 {
102 if j := strings.LastIndex(css[:i], "prefers-color-scheme"); j < 0 {
103 t.Error("a chroma token rule sits outside any scheme scope")
104 }
105 }
94 // The +/- markers are CSS, so a copied selection is real source. 106 // The +/- markers are CSS, so a copied selection is real source.
95 if strings.Contains(body, `<td class="src">+`) { 107 if strings.Contains(body, `<td class="src">+`) {
96 t.Error("diff markers are in the markup, not the stylesheet") 108 t.Error("diff markers are in the markup, not the stylesheet")
internal/httpd/web.go +8 −4
@@ -829,13 +829,17 @@ func highlight(filePath string, data []byte) template.HTML {
829 return template.HTML(buf.String()) 829 return template.HTML(buf.String())
830} 830}
831 831
832// chromaCSS is both syntax palettes: light by default, dark under the same 832// chromaCSS is both syntax palettes, each scoped to the scheme it is for.
833// media query the rest of the stylesheet uses. The site's --code-bg stays 833// The light one cannot be left unscoped: the two palettes do not name the
834// the background either way. 834// same token set, and every token github-dark omits would keep its
835// light-theme colour on a black ground — NameAttribute landed at 2.97:1.
836// Scoped, an unnamed token inherits the wrapper's colour instead, which is
837// readable in both. The site's --code-bg stays the background either way.
835var chromaCSS = func() []byte { 838var chromaCSS = func() []byte {
836 var buf bytes.Buffer 839 var buf bytes.Buffer
840 buf.WriteString("@media (prefers-color-scheme: light) {\n")
837 chromaFormatter.WriteCSS(&buf, styles.Get("friendly")) 841 chromaFormatter.WriteCSS(&buf, styles.Get("friendly"))
838 buf.WriteString("\n@media (prefers-color-scheme: dark) {\n") 842 buf.WriteString("}\n@media (prefers-color-scheme: dark) {\n")
839 chromaFormatter.WriteCSS(&buf, styles.Get("github-dark")) 843 chromaFormatter.WriteCSS(&buf, styles.Get("github-dark"))
840 buf.WriteString("}\n.chroma, .bg { background: transparent !important; }\n") 844 buf.WriteString("}\n.chroma, .bg { background: transparent !important; }\n")
841 return buf.Bytes() 845 return buf.Bytes()