Docs

Browser Color-Scheme Isolation — Delivery Notes

Delivery summary for making Tranquil’s browser tabs render like a conventional browser even though the editor forces nativeTheme.themeSource to match its own (usually dark) chrome. This note says what shipped and where.

The problem

Pulsar’s theme-manager (src/theme-manager.js refreshWindowTheme) forces nativeTheme.themeSource to "dark"/"light" so native chrome (menus, scrollbars) matches the editor theme. But themeSource is app-global — it also drives what every web frame sees, including the browser’s <webview> guests. Electron 30 has no per-webContents color-scheme flag, so the only lever is the Chromium DevTools Protocol: attach the debugger to each guest and override rendering there.

There are two distinct symptoms, and they need two different CDP overrides — fixing one does not fix the other:

SymptomCauseLever
Site renders darker than normal (it ships a dark stylesheet)prefers-color-scheme: dark reported to the guestEmulation.setEmulatedMedia
Editor’s dark backdrop shows through the siteSite sets no html/body background, so Chromium paints its default canvas — which follows the forced-dark themeSourceEmulation.setDefaultBackgroundColorOverride

The first override only satisfies media queries. A page that never asks about the color scheme — e.g. a bare Markdown-export page whose body sets only margin and font — has no media query to satisfy, so its canvas just inherits the dark base color and the editor chrome bleeds through. The confirming example was qount25.dev/Shrimple/: its stylesheet sets only margin / line-height / font on body, no background-color anywhere.

What shipped

Both overrides are pinned to a light guest (a browser’s conventional default and the “original” appearance sites were reported to lose):

  • Emulation.setEmulatedMediaprefers-color-scheme: light
  • Emulation.setDefaultBackgroundColorOverride → opaque white ({ r:255, g:255, b:255, a:1 })

They are applied together in applyScheme, so they share one lifecycle:

  • Re-primed on every navigationattach() (not just applyScheme()) runs on dom-ready and did-finish-load, so a failed initial attach (guest CDP target not ready at creation) is retried and a cross-process navigation that resets the overrides gets them back.
  • Released/reclaimed around native DevTools — opening DevTools on a <webview> needs the single CDP client free; Electron auto-detaches our debugger, and we reclaim + re-apply on devtools-closed.
  • Only webContents of type 'webview' (the live guest tabs) are managed. HAR capture attaches its own debugger to a hidden capture window (type: 'window'), never a live tab, so the two never contend for the one CDP client a webContents allows.

Where it lives

  • tranquil-clientsrc/main-process/tb-color-scheme.jsmanageBrowserColorScheme(wc); the GUEST_SCHEME and GUEST_DEFAULT_BG constants; attach / re-prime / DevTools handling.
  • tranquil-clientsrc/main-process/cz-init.js — wires guest webContents into manageBrowserColorScheme.

Constraints / follow-ups

  • Pinned to light, not OS-following. A real browser would follow the OS appearance. The clean API for reading the OS setting while themeSource is overridden (nativeTheme.shouldUseDarkColorsForSystemIntegratedUI) doesn’t exist in Electron 30. Revisit and switch to OS-following after the Pulsar/Electron upgrade — at which point GUEST_DEFAULT_BG should track the chosen scheme (white for light, a dark canvas for dark) instead of being constant white.

Verified

  • prefers-color-scheme override: a site with a dark media-query stylesheet renders in its light appearance in a browser tab. ✅
  • default-background override: qount25.dev/Shrimple/ (no body background) renders on a white canvas instead of showing the dark editor backdrop. ✅ (confirmed live)