mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
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:
co-authored by
Claude Opus 5
parent
5fb834e3a4
commit
5d399c3d31
@@ -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>`
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user