Skip to content

Enable panel-live in HTML and Markdown pane#28

Merged
MarcSkovMadsen merged 3 commits intomainfrom
enhancement/html
Feb 22, 2026
Merged

Enable panel-live in HTML and Markdown pane#28
MarcSkovMadsen merged 3 commits intomainfrom
enhancement/html

Conversation

@MarcSkovMadsen
Copy link
Contributor

@MarcSkovMadsen MarcSkovMadsen commented Feb 22, 2026

PR: Shadow DOM support, panes reference app, and prerender BrokenPipeError fix

Summary

  • <panel-live> now works inside pn.pane.HTML, pn.pane.Markdown, and pn.chat.ChatInterface — Panel panes that render inside Bokeh's Shadow DOM.
  • New how-to guide, reference app, and panel-live serve route documenting the three embedding patterns.
  • Subprocess pre-rendering no longer prints spurious BrokenPipeError tracebacks when geoviews (or any slow import) times out.
  • 7 new JS unit tests for the shadow DOM helpers; 1 new Python test for the terminate-on-timeout fix.

What changed

Shadow DOM support (lib/panel-live-element.js)

Bokeh's embed_items() calls document.getElementById() on the main document. When <panel-live> is inside a Shadow DOM (as happens with pn.pane.HTML and pn.pane.Markdown), the output container #el-XXXX is invisible to the main-document lookup and Bokeh's render fails silently.

Two module-level helpers fix this:

  • _patchGetElementByIdForPL(shadowRoot) — wraps document.getElementById once so it falls back to searching all known shadow roots. Idempotent: a second <panel-live> connecting to a different shadow root simply adds that root to the set; the wrapper is not double-applied.
  • _injectPanelLiveCSSIntoShadowRoot(shadowRoot) — clones the panel-live.css <link> from <head> into the shadow root so the component renders correctly. Falls back to deriving the CSS URL from the panel-live.js <script> src if no <link> is found.

Both run synchronously in connectedCallback before the requestAnimationFrame render block. Detection is automatic — no attribute or configuration needed.

How-to guide (docs/how-to/panel-panes.md)

New page documenting three embedding patterns:

  1. pn.pane.HTML — raw <panel-live> string, editor mode
  2. pn.pane.Markdown — passes raw HTML blocks through unchanged
  3. pn.chat.ChatInterface — callback returns pn.pane.HTML('<panel-live ...>')

Covers three asset-loading options (GitHub Pages CDN, jsDelivr npm CDN, local --static-dirs). Added to nav under "Panel Component" in zensical.toml. Cross-referenced from panel-component.md and getting-started-panel.md.

Reference app (src/panel_live/examples/panes.py)

Three-section pn.Accordion app modelled on showcase.py, demonstrating HTML pane, Markdown pane, and ChatInterface. Uses pn.extension(js_files=..., css_files=...) to load the web component from CDN — no PanelLive JSComponent import.

panel-live serve now serves both:

  • http://localhost:5008/ — Showcase (all 6 modes + server RPC)
  • http://localhost:5008/panes — HTML, Markdown & ChatInterface panes

Prerender BrokenPipeError fix (src/panel_live/prerender.py)

proc.join(timeout=N) returns without killing the subprocess when the timeout expires. The subprocess keeps running; when it eventually finishes it tries conn.send(), but the parent's pipe end has been closed → BrokenPipeError printed to the terminal (seen with slow/broken imports like import geoviews as gv).

Fix:

child_conn.close()           # parent closes the write end it doesn't use
proc.join(timeout=timeout)
if proc.is_alive():
    proc.terminate()         # kill zombie so it can't write to a closed pipe
    proc.join(timeout=5)

JS unit tests (tests/js/unit/panel-live-element.test.js)

7 new tests covering the shadow DOM helpers, ordered to respect module-level state accumulation:

  • Group 1 (runs first, unpatched state): main-document connection does NOT patch getElementById
  • Group 2: first shadow root patches getElementById; original lookups still work
  • Group 3: second shadow root added to set without re-wrapping (idempotency)
  • Group 4: CSS injection from <head> link clone, script-src fallback, and no-crash when neither present

Python test update (tests/test_prerender.py)

  • test_pre_render_timeout_forwarded — adds is_alive.return_value = False to correctly model a finished process
  • test_pre_render_timeout_terminates_subprocess — new test: asserts proc.terminate() is called and proc.join(timeout=5) is invoked after a timeout

Files changed

13 files, ~742 insertions in this increment:

  • lib/panel-live-element.js — shadow DOM helpers + connectedCallback detection
  • docs/how-to/panel-panes.md — new how-to guide (created)
  • src/panel_live/examples/panes.py — new reference app (created)
  • src/panel_live/cli.py/panes route + updated print output
  • zensical.toml — nav entry "HTML & Markdown Panes"
  • docs/how-to/panel-component.md — "See also" cross-reference
  • docs/tutorials/getting-started-panel.md — "Next Steps" cross-reference
  • tests/js/unit/panel-live-element.test.js — 7 new JS tests (created)
  • src/panel_live/prerender.py — terminate subprocess on timeout
  • tests/test_prerender.py — updated + 1 new test

Testing

  • pixi run test — 179 passed, 4 skipped
  • pixi run test-js — 185 passed (13 test files)
  • pixi run lint — all checks passed

@MarcSkovMadsen MarcSkovMadsen merged commit 777e441 into main Feb 22, 2026
16 of 19 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.

1 participant