Skip to content

Dark-mode the document background, not just the rail, update text fonts in dark mode#6

Open
AnnaXWang wants to merge 4 commits into
mainfrom
hypeship/unify-doc-rail-scroll
Open

Dark-mode the document background, not just the rail, update text fonts in dark mode#6
AnnaXWang wants to merge 4 commits into
mainfrom
hypeship/unify-doc-rail-scroll

Conversation

@AnnaXWang

@AnnaXWang AnnaXWang commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

The light/dark toggle themed the bar and rail but left the author's document (rendered in the sandboxed iframe) untouched, so picking dark left the doc area light — visibly inconsistent.

This forwards the toggle mode into the iframe via a new jh:themeMode message. The overlay (the only code that can touch the opaque-origin document) forces the document's color-scheme + background:

  • dark/light force the doc canvas + default text (!important, so an explicit pick wins over an authored background); auto removes the override and restores the doc exactly as authored.
  • The overlay re-samples after applying, so the chrome palette and dark-highlight treatment follow the document through the existing jh:theme round-trip — bar, rail, doc, and highlights all end up consistent.

Small, presentation-only change: +9 lines in the shell (one effect), +39 in the overlay (a jh:themeMode handler that sets color-scheme and an injected bg/fg style). No API/DB/anchoring changes.

Inherent limits (documented at the injection site)

  • Per-element authored colors still cascade — we can't safely invert an arbitrary design, so element-level color declarations are left alone.
  • @media (prefers-color-scheme) can't be driven from script, so a doc that themes itself only via that media query won't respond to the toggle.

Verification

  • tsc clean, vitest 108/108, next build clean.
  • Harness with a UA-default doc and an explicitly-light-authored doc: forced-dark flips both canvases dark with body text inverted; forced-light normalizes to white; auto restores the authored look.

Note: this PR was scoped down to just the dark-mode document change. The earlier single-scrollbar work (and its iframe-height-ratchet fix) is preserved on the hypeship/unify-scroll-archive branch, out of this PR.


Note

Low Risk
Presentation-only iframe DOM/CSS changes with no API or persistence impact; edge cases around authored colors and media-query-only docs are documented and intentionally limited.

Overview
The light/dark/auto control used to recolor only the shell chrome, so dark could leave the authored document in the iframe looking light. The shell now sends jh:themeMode to the sandbox overlay whenever the viewer’s theme preference changes.

Inside the overlay, forced dark or light sets color-scheme, injects page background styles, and walks the DOM to recolor body text on the main canvas while skipping code blocks, elements with their own backgrounds, links, and highlight segments. Auto tears down those overrides. After applying, sampleTheme reports forced bg/fg so rail/cards stay aligned via the existing jh:theme path, and the shell’s DEFAULT_DARK fallback foreground is white instead of gray.

Reviewed by Cursor Bugbot for commit e728b12. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
justhtml Ready Ready Preview, Comment Jul 2, 2026 3:46am

@AnnaXWang AnnaXWang changed the title Unify the doc and comment rail into one scrollbar Viewer: unify doc+rail scroll, and dark-mode the document Jul 2, 2026
@AnnaXWang AnnaXWang closed this Jul 2, 2026
The light/dark toggle themed the bar and rail but left the author's
document (rendered in the sandboxed iframe) untouched, so picking dark
left the doc area light — visibly inconsistent.

Forward the toggle mode into the iframe via a new jh:themeMode message.
The overlay (the only code that can touch the opaque-origin document)
forces the doc's color-scheme and, with !important, its background/text
so an explicit pick wins over an authored background; "auto" removes the
override and restores the doc exactly as authored. The overlay re-samples
after applying, so the chrome palette and dark-highlight treatment follow
the document through the existing jh:theme round-trip.

Per-element authored colors still cascade (we can't invert an arbitrary
design) and @media(prefers-color-scheme) can't be driven from script;
both are inherent and documented at the injection site.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@AnnaXWang AnnaXWang changed the title Viewer: unify doc+rail scroll, and dark-mode the document Dark-mode the document background, not just the rail Jul 2, 2026
@AnnaXWang AnnaXWang reopened this Jul 2, 2026
@AnnaXWang AnnaXWang force-pushed the hypeship/unify-doc-rail-scroll branch from a0fbf0c to 0207d85 Compare July 2, 2026 03:05
@AnnaXWang AnnaXWang changed the title Dark-mode the document background, not just the rail Dark-mode the document background, not just the rail, update text fonts in dark mode Jul 2, 2026
The forced-dark override set body text to #c9d1d9 (a light gray); use
#ffffff so default document text is white on the dark canvas.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Setting body color white didn't work: authored element rules (p,li
{color:#1a1a1a}, th{color:#444}) beat inheritance, so most text stayed
dark. But blanket-whitening every element breaks anything with its own
light background — code chips, pill badges, callout boxes would render
white-on-light.

Walk the DOM instead: recolor the text of every element sitting on the
page background, and skip any element with its own background (or a code
block) plus its subtree — generalizing "leave code alone" to badges and
boxes too. A first pass pins each such surface's authored text color
inline (so a whitened ancestor can't leak white into a code chip that
inherits its color). Links keep their accent.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The rail/comment-card colors are derived from the doc's sampled fg. On a
light doc forced dark, that fg is the authored dark text lifted only to
AA — a gray, so comment text read gray, not white. When the viewer forces
a theme, report the forced fg/bg from the overlay's sample so the chrome
palette matches the forced document (white comment text in dark), and set
the DEFAULT_DARK fallback fg to white for the pre-sample moment. Auto mode
still samples the doc so the chrome adapts to genuinely-dark docs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@AnnaXWang AnnaXWang marked this pull request as ready for review July 2, 2026 03:47
@AnnaXWang AnnaXWang requested a review from rgarcia July 2, 2026 03:48
Comment thread lib/docs/overlay.ts
var c = kids[i], tag = c.tagName;
if (tag === "SCRIPT" || tag === "STYLE") continue;
if (isSurface(c, tag)){
if (!c.hasAttribute("data-jh-fg-pin")){

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forcing dark mode on a light doc pins highlight segments' authored dark text color inline, leaving them as near-invisible dark-on-dark text once the segment background turns transparent under jh-dark.

Fix on Vercel

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e728b12. Configure here.

Comment thread lib/docs/overlay.ts
+ "html.jh-force-dark .jh-doc-fg{color:#fff!important}"
+ "html.jh-force-light{color-scheme:light}"
+ "html.jh-force-light,html.jh-force-light body{background-color:#ffffff!important}"
+ "html.jh-force-light .jh-doc-fg{color:#111!important}";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forced theme skips highlight text

Medium Severity

When the viewer forces dark or light, recoloring only applies via .jh-doc-fg, but whitenPage deliberately skips span[data-jh-seg] highlight wrappers. Those spans still match authored span (or similar) color rules, so anchored comment text can stay dark on the forced-dark canvas while underlines-only highlights remain.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e728b12. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant