Skip to content

Scope the widget's NDL stylesheet so it cannot restyle the host page - #468

Merged
FlorentinD merged 4 commits into
mainfrom
gds-440-marimo-integration-is-off
Oct 9, 2026
Merged

FlorentinD merged 4 commits into
mainfrom
gds-440-marimo-integration-is-off

Conversation

@FlorentinD

Copy link
Copy Markdown
Collaborator

Stacked on #467 (base branch gdsa-1733-neo4j-viz-graphwidget-not-rendering-in-graph-analytics).

Problem

The graph widget restyles its host page in Marimo notebooks: the widget renders fine, but Marimo's own typography, links and lists get reset and the page turns white while Marimo stays in dark mode.

Root cause

The widget applies NDL's full stylesheet at the host document level — the overlay copy in document.head used for portaled menus, and _css where the host mounts it document-wide. That stylesheet's Tailwind preflight has bare-element selectors (h1, a, ol,ul,menu, *, body, …) and declares color-scheme plus the lightningcss light/dark toggles on :root. Against Marimo's own low-specificity element styles the preflight wins, and the root theme-state flips Marimo's light-dark() resolution.

Fix

Scope the NDL stylesheet to the widget at build time (a Vite plugin applied to all three builds):

  • Bare-element/global selectors are rewritten to match only inside the wrapper (:where([data-neo4j-viz-ndl]) …), so specificity and the cascade inside the widget are unchanged.
  • The root theme-state declarations (color-scheme, --lightningcss-*) are relocated from :root/:host to the wrapper.
  • Class-scoped rules (.ndl-*, .cm-*, …) stay global, so overlays portaled outside the wrapper (menus, tooltips) keep their styling.

Testing

  • ndl-css-scope.test.ts — transformer unit tests.
  • test_widget_marimo_browser.py — new Playwright e2e against a real Marimo server; asserts the document-level NDL stylesheet has no bare-element or root theme-state rules.
  • test_widget_browser.py — same assertion added to the JupyterLab e2e.
  • test_style_css_scope.py — direct guard on the shipped style.css.
  • marimo moved to the dev group so the integration test runs in CI.

Verified the guards fail against the pre-fix stylesheet (100 bare selectors + 3 theme-state) and pass after. just py-test (323 passed) and just js-test (67 passed) are green.

@FlorentinD
FlorentinD added this pull request to stack #469 October 8, 2026 08:28
@FlorentinD
FlorentinD marked this pull request as draft October 8, 2026 08:28
Comment thread js-applet/src/ndl-css-scope.ts Outdated
// Leading/trailing whitespace and comments, kept verbatim so a license header
// on the first selector of a list survives and never leaks into the selector.
const LEADING_RE = /^(?:\s|\/\*[\s\S]*?\*\/)*/;
const TRAILING_RE = /(?:\s|\/\*[\s\S]*?\*\/)*$/;
Base automatically changed from gdsa-1733-neo4j-viz-graphwidget-not-rendering-in-graph-analytics to main October 9, 2026 14:02
The widget applies NDL's full stylesheet at the host document level: the
overlay copy in document.head used for portaled menus, and `_css` where the
host mounts it document-wide. That stylesheet's Tailwind preflight has
bare-element selectors (h1, a, ol/ul/menu, *, body, ...) and declares
color-scheme and the lightningcss light/dark toggles on :root, so it reset the
host page's own typography, links and lists and flipped Marimo's light/dark
resolution (leaving the page white while Marimo stayed dark).

Scope the stylesheet to the widget at build time. Bare-element selectors are
rewritten to match only inside the wrapper (via :where() so specificity and the
cascade are unchanged), and the root theme-state declarations are relocated to
the wrapper. Class-scoped rules stay global, so overlays portaled outside the
wrapper keep their styling.
Cover the scoping end-to-end in real Marimo and JupyterLab sessions, asserting
the document-level NDL stylesheet has no bare-element or root theme-state rules,
and guard the shipped stylesheet directly.

Move marimo to the dev group so the Marimo integration test runs in CI (it is
not a notebook-only dependency). The marimo example recipes rely on the implicit
default group, matching the streamlit recipe.
@FlorentinD
FlorentinD marked this pull request as ready for review October 9, 2026 14:06
@FlorentinD
FlorentinD force-pushed the gds-440-marimo-integration-is-off branch from 6773879 to 52c588e Compare October 9, 2026 14:13
Replace the backtracking-prone leading/trailing selector regexes with a linear
edge scan, drop the second declaration mini-parser in favour of a plain split of
the root token blocks, and reduce the shipped-stylesheet guard to targeted regex
checks instead of a reimplemented CSS tokenizer.

Behaviour is unchanged: the rebuilt resources are byte-identical.
Replace the hand-rolled CSS tokenizer (brace/string/comment scanning, selector
list splitting) with postcss. postcss already ships in the tree via vite, so
this adds no new packages, and it handles the structure (comments, at-rules,
nested rules, paren-aware selector lists) that both of the bugs we hit during
development lived in. Only the scoping policy stays ours.

Behaviour is unchanged: the rebuilt resources are byte-identical.
@FlorentinD
FlorentinD merged commit 01bbd47 into main Oct 9, 2026
16 checks passed
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.

2 participants