Commit 846b17ebe7

846b17ebe7687ac4d34c9bffc2276da0a38ce28e

parent: cf58aba82d

Verified · cmc ci/build: success

cmc <hello@cleberg.net> · 2026-08-26 22:56 UTC

web: diff syntax highlighting was never applying

chroma scopes every token rule under .chroma, and the per-line markup
has no wrapper of its own, so the token classes matched nothing and each
line inherited one flat colour. The cell is the wrapper now.

That flat colour came from details.difffold .add/.del, meant for the
+N/-M counts in the fold summary but also matching tr.add/tr.del; those
are scoped to the summary.

The old test asserted the token classes existed, which they did — the
new one checks the cell is a chroma wrapper and that the stylesheet has
rules to match.

Layout: unified · split

e2e/diffweb_test.go +20
@@ -82,6 +82,15 @@ func TestDiffRendering(t *testing.T) {
82 if !strings.Contains(body, "class=\"k\"") && !strings.Contains(body, "class=\"kd\"") { 82 if !strings.Contains(body, "class=\"k\"") && !strings.Contains(body, "class=\"kd\"") {
83 t.Error("diff content is not syntax highlighted") 83 t.Error("diff content is not syntax highlighted")
84 } 84 }
85 // Token classes alone prove nothing: chroma scopes every colour rule
86 // under .chroma, and the per-line markup has no wrapper of its own, so
87 // the cell has to be that wrapper or the classes are inert.
88 if !strings.Contains(body, `<td class="src chroma">`) {
89 t.Error("diff cell is not a chroma wrapper, so token classes go unstyled")
90 }
91 if !strings.Contains(stylesheet(t, inst), ".chroma .k") {
92 t.Error("stylesheet has no chroma token rules to match")
93 }
85 // The +/- markers are CSS, so a copied selection is real source. 94 // The +/- markers are CSS, so a copied selection is real source.
86 if strings.Contains(body, `<td class="src">+`) { 95 if strings.Contains(body, `<td class="src">+`) {
87 t.Error("diff markers are in the markup, not the stylesheet") 96 t.Error("diff markers are in the markup, not the stylesheet")
@@ -107,3 +116,14 @@ func jsonField(blob, name string) string {
107 } 116 }
108 return rest[:j] 117 return rest[:j]
109} 118}
119
120// stylesheet fetches the served CSS, so a test can check that a rule the
121// markup depends on actually exists.
122func stylesheet(t *testing.T, inst *instance) string {
123 t.Helper()
124 status, body := inst.get(t, "/static/style.css")
125 if status != 200 {
126 t.Fatalf("stylesheet: %d", status)
127 }
128 return body
129}
internal/web/static/style.css +2 −2
@@ -917,8 +917,8 @@ details.difffold summary:hover { background: var(--fill-subtle); }
917summary .fpath { font-family: var(--mono); overflow-wrap: anywhere; } 917summary .fpath { font-family: var(--mono); overflow-wrap: anywhere; }
918summary .fpath .was { color: var(--muted); } 918summary .fpath .was { color: var(--muted); }
919summary .fstat { margin-left: auto; font-family: var(--mono); white-space: nowrap; } 919summary .fstat { margin-left: auto; font-family: var(--mono); white-space: nowrap; }
920details.difffold .add { color: var(--ok); } 920details.difffold summary .add { color: var(--ok); }
921details.difffold .del { color: var(--bad); } 921details.difffold summary .del { color: var(--bad); }
922details.difffold > .none { padding: var(--sp-3); margin: 0; } 922details.difffold > .none { padding: var(--sp-3); margin: 0; }
923details.difffold .tablewrap { border-radius: 0 0 var(--r-md) var(--r-md); } 923details.difffold .tablewrap { border-radius: 0 0 var(--r-md) var(--r-md); }
924 924
internal/web/templates/layout.html +1 −1
@@ -128,7 +128,7 @@
128 {{else}}<div class="tablewrap"><table class="difftable"> 128 {{else}}<div class="tablewrap"><table class="difftable">
129 {{range .Lines}}{{if eq .Class "hunk"}}<tr class="hunk"><td class="ln" colspan="2"></td><td class="src">{{.Text}}</td></tr> 129 {{range .Lines}}{{if eq .Class "hunk"}}<tr class="hunk"><td class="ln" colspan="2"></td><td class="src">{{.Text}}</td></tr>
130 {{else if eq .Class "meta"}}<tr class="dmeta"><td class="ln" colspan="2"></td><td class="src">{{.Text}}</td></tr> 130 {{else if eq .Class "meta"}}<tr class="dmeta"><td class="ln" colspan="2"></td><td class="src">{{.Text}}</td></tr>
131 {{else}}<tr class="{{.Class}}"><td class="ln">{{if .OldLine}}{{.OldLine}}{{end}}</td><td class="ln">{{if .NewLine}}{{.NewLine}}{{end}}</td><td class="src">{{if .Code}}{{.Code}}{{else}}{{.Content}}{{end}}</td></tr> 131 {{else}}<tr class="{{.Class}}"><td class="ln">{{if .OldLine}}{{.OldLine}}{{end}}</td><td class="ln">{{if .NewLine}}{{.NewLine}}{{end}}</td><td class="src chroma">{{if .Code}}{{.Code}}{{else}}{{.Content}}{{end}}</td></tr>
132 {{end}}{{range .Threads}}<tr class="threadrow"><td colspan="3"><div class="thread{{if .Resolved}} resolved{{end}}">{{if .Resolved}}<p class="threadstate">resolved by {{.Resolved}}</p>{{end}}{{range .Comments}}<p class="commenthead"><strong>{{.Author}}</strong> <span class="when">{{when .CreatedAt}}</span></p><div class="rendered">{{.BodyHTML}}</div>{{end}}{{template "threadact" dict "ID" .ID "Resolved" .Resolved "Base" $base "Can" $can}}</div></td></tr> 132 {{end}}{{range .Threads}}<tr class="threadrow"><td colspan="3"><div class="thread{{if .Resolved}} resolved{{end}}">{{if .Resolved}}<p class="threadstate">resolved by {{.Resolved}}</p>{{end}}{{range .Comments}}<p class="commenthead"><strong>{{.Author}}</strong> <span class="when">{{when .CreatedAt}}</span></p><div class="rendered">{{.BodyHTML}}</div>{{end}}{{template "threadact" dict "ID" .ID "Resolved" .Resolved "Base" $base "Can" $can}}</div></td></tr>
133 {{end}}{{end}} 133 {{end}}{{end}}
134 </table></div>{{end}} 134 </table></div>{{end}}