Skip to content

fix(pyscript): use MutationObserver to adopt styles into CodeMirror shadow roots#109

Merged
xransum merged 2 commits intomasterfrom
fix/pyscript-shadow-dom-mutation-observer
Mar 14, 2026
Merged

fix(pyscript): use MutationObserver to adopt styles into CodeMirror shadow roots#109
xransum merged 2 commits intomasterfrom
fix/pyscript-shadow-dom-mutation-observer

Conversation

@xransum
Copy link
Copy Markdown
Owner

@xransum xransum commented Mar 14, 2026

Summary

  • Replace the broken Element.prototype.attachShadow monkey-patch with a MutationObserver that watches for .py-editor-input nodes being inserted into the DOM and adopts the shared CSSStyleSheet into the shadow root that PyScript has already attached by that point.
  • Fix token class names in buildCSS(): CodeMirror 6 emits tok-* class names via @lezer/highlight's classHighlighter, not the legacy cm-* names from CodeMirror 5.
  • Fix the SVG run button icon disappearing in dark mode by overriding PyScript's hardcoded fill="#464646" with fill: currentColor.

Root causes

The monkey-patch approach had two independent failure modes:

  1. Wrong element: PyScript attaches the shadow root to an anonymous <div> inside .py-editor-input, not on .py-editor-input itself. The class check on this (the element calling attachShadow) never matched because that element has no classes.

  2. Patch overwritten: PyScript's core.js also patches Element.prototype.attachShadow via queueMicrotask, which runs after our synchronous script and silently replaces our patch with its own.

Fix

PyScript builds the full editor tree off-DOM, calls attachShadow on the inner div, then appends the whole tree to the page. By the time the MutationObserver fires on the inserted .py-editor-box, host.shadowRoot already exists and can be adopted into immediately -- no race condition.

…hadow roots

Replace the Element.prototype.attachShadow monkey-patch with a
MutationObserver approach. The patch was broken for two reasons:
1. PyScript attaches the shadow root to an anonymous <div> inside
   .py-editor-input, not on .py-editor-input itself, so the class
   check on `this` never matched.
2. PyScript core.js also patches attachShadow via queueMicrotask,
   running after our synchronous script and overwriting our patch.

The observer watches document.body for .py-editor-input nodes being
inserted (PyScript builds the full tree off-DOM, calls attachShadow,
then appends), finds the shadow host child div, and adopts the shared
CSSStyleSheet into its already-attached shadow root.

Also fix token class names in buildCSS(): CodeMirror 6 uses tok-*
class names from @lezer/highlight classHighlighter, not the legacy
cm-* names from CodeMirror 5.

Also fix the SVG run button icon in dark mode: override PyScript's
hardcoded fill="#464646" on the path element with fill:currentColor
so the icon inherits the button's themed color variable.
@xransum xransum added bug Something isn't working javascript Pull requests that update Javascript code labels Mar 14, 2026
@xransum xransum self-assigned this Mar 14, 2026
@xransum xransum merged commit a519961 into master Mar 14, 2026
4 checks passed
@xransum xransum deleted the fix/pyscript-shadow-dom-mutation-observer branch March 14, 2026 04:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working javascript Pull requests that update Javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant