fix(webapp): share page dark mode no longer shows white slabs (BEA-71) (#122)

The share page already followed the system, but its dark @media block sat
ABOVE the light pre/code/blockquote/table rules at equal specificity, so
every light rule after it won. A dark-system visitor got a white frontmatter
slab, a white code block and light-grey table borders on a near-black page —
the surface strangers see first reading as a different product.

Source order is the whole fix: the block moves to the end of the stylesheet
and gets completed there, which repairs pre/code for free. Values are the
hub's @theme tokens from frontend/src/tw.css instead of the hand-picked greys
(#c6cbd3 body text, #3a3a44/#888 footer) that were the "different product"
half of the complaint.

Light mode is pixel-identical — only the dark block moved. Response headers
(sandbox CSP, nosniff, referrer) are untouched.

The test asserts placement, not presence: "a dark rule exists" passed while
the page was still wrong, so it checks the dark block comes after the last
light literal.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Snow Lee (Sungwon)
2026-08-05 17:06:50 +09:00
committed by GitHub
co-authored by Claude Opus 5
parent 5fb834e3a4
commit 5d399c3d31
2 changed files with 56 additions and 3 deletions
+16 -3
View File
@@ -524,8 +524,6 @@ const sharedMarkdownShell = `<!doctype html><html lang="en"><head><meta charset=
body{font:16px/1.7 -apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",sans-serif;color:#24292f;
max-width:720px;margin:0 auto;padding:52px 24px 96px}
a{color:#b26a00}
@media (prefers-color-scheme: dark){body{background:#0a0b0d;color:#c6cbd3}
a{color:#ffcf85}code,pre{background:#15171b}h1,h2,h3{color:#f4f6f9}}
h1,h2,h3{line-height:1.25;letter-spacing:-.018em}
pre{padding:12px;border-radius:8px;overflow-x:auto;background:#f6f8fa}
code{background:#f6f8fa;padding:2px 5px;border-radius:4px;font-size:.9em}
@@ -542,7 +540,22 @@ pre{max-width:100%%}
footer.bdrive{margin-top:64px;padding-top:14px;border-top:1px solid #d0d7de;font-size:12.5px;color:#57606a}
footer.bdrive a{color:inherit}
.updated{font-size:12.5px;color:#57606a;margin-bottom:28px}
@media (prefers-color-scheme: dark){footer.bdrive{border-color:#3a3a44;color:#888}.updated{color:#888}}
/* Dark theme LAST: these rules sit at the same specificity as the light ones
above, so source order is the whole fix — a dark block placed earlier loses
to every light rule that follows it. Values are the hub's @theme tokens
(frontend/src/tw.css), never hand-picked, so the two surfaces agree. */
@media (prefers-color-scheme: dark){
body{background:#0a0b0d;color:#eef0f3}
a{color:#ffcf85}
h1,h2,h3{color:#eef0f3}
pre,code{background:#15171b}
blockquote{border-left-color:rgba(255,255,255,.07);color:#9aa0a9}
td,th{border-color:rgba(255,255,255,.07)}
table.frontmatter{background:#15171b;color:#9aa0a9}
table.frontmatter th,table.frontmatter td{border-bottom-color:rgba(255,255,255,.07)}
table.frontmatter th{color:#868b93}
footer.bdrive{border-top-color:rgba(255,255,255,.07);color:#868b93}
.updated{color:#868b93}}
</style></head><body>%s%s
<footer class="bdrive">Shared with <a href="https://github.com/runbear-io/beardrive" rel="noopener">BearDrive</a> — synced files for AI agent teams</footer>
</body></html>`
+40
View File
@@ -433,6 +433,46 @@ func TestShareLastUpdatedStamp(t *testing.T) {
}
}
// TestShareDarkThemeIsLast: the share page is the surface strangers see first,
// so in dark mode it must not show white slabs. Every dark rule sits at the
// same specificity as the light one it overrides, which makes SOURCE ORDER the
// whole feature — a dark block placed before the light rules (as it was) loses
// silently and the page still renders light. Assert placement, not presence.
func TestShareDarkThemeIsLast(t *testing.T) {
srv, p, _, f, h := shareHub(t)
f.put("dev1", "wiki/themed.md", "---\ntitle: Q3\n---\n\n# Q3\n\n> quote\n\n| a | b |\n| - | - |\n| 1 | 2 |\n\n```go\nx := 1\n```\n")
token, _ := authedShare(t, srv, h, p.ID, "wiki/themed.md")
body := do(t, h, "GET", "/s/"+token, nil).Body.String()
if strings.Contains(body, "%!") {
t.Fatalf("format verb leaked into the page (a %% needs doubling in the const): %s", body)
}
dark := strings.LastIndex(body, "prefers-color-scheme")
if dark < 0 {
t.Fatal("no dark block at all")
}
// Every light surface colour must be settled before the dark block opens.
for _, light := range []string{"#f6f8fa", "#d0d7de", "#d8dee4", "#6e7781", "#57606a"} {
if i := strings.LastIndex(body, light); i > dark {
t.Errorf("light literal %s at %d comes after the dark block at %d — it wins in dark mode", light, i, dark)
}
}
// ...and the dark block has to actually cover the surfaces that were light.
block := body[dark:]
for _, sel := range []string{"pre,code{background:#15171b}", "blockquote{", "td,th{", "table.frontmatter{", "footer.bdrive{"} {
if !strings.Contains(block, sel) {
t.Errorf("dark block has no rule for %q", sel)
}
}
// Hub tokens, not a hand-picked palette (tw.css: --color-text, --color-bg).
if !strings.Contains(block, "background:#0a0b0d;color:#eef0f3") {
t.Error("dark body must use the hub's bg/text tokens")
}
if strings.Contains(body, "#c6cbd3") || strings.Contains(body, "#3a3a44") {
t.Error("ad-hoc dark greys survived; use the tw.css tokens")
}
}
func jsonReq(t *testing.T, method, url string, body any) *http.Request {
t.Helper()
var data []byte