Skip to content

feat(thebe): enable in-page live compute via JupyterLite (Phase 2) - #98

Open
mmcky wants to merge 13 commits into
mainfrom
feat/thebe-live-compute
Open

feat(thebe): enable in-page live compute via JupyterLite (Phase 2)#98
mmcky wants to merge 13 commits into
mainfrom
feat/thebe-live-compute

Conversation

@mmcky

@mmcky mmcky commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Enables opt-in in-page live compute via the standard MyST thebe frontmatter, completing Phase 2 (launch parity). The execution infra was already wired in the theme (ComputeOptionsProvider + ThebeLoaderAndServer in Page.tsx; ExecuteScopeProvider + NotebookToolbar in PageContent.tsx) and gated behind project.thebe — so this turns it on, sets the QuantEcon default, relocates the control into the header, verifies it end-to-end, and documents it.

Final design: the header Power toggle

Setting project.thebe surfaces a Power toggle in the QuantEcon header toolbar next to Launch on notebook pages (desktop). Clicking it boots the kernel; Run / Restart / Clear then take its place and cells execute live. The control is the @myst-theme/jupyter NotebookToolbar, relocated via a React portal: ComputeToolbarSlot.tsx (rendered from PageContent, inside the Thebe providers) portals it into #qe-compute-slot, an empty:hidden <li> in Toolbar.tsx. The portal keeps the component inside the providers — React context flows through the React tree, not the DOM — even though the header mounts outside them, so no provider-tree lift is needed. styles/app.css neutralises the toolbar's default floating-pill styling and matches the 20px header icon size.

The placement went through review iterations on this thread (default floating pill → labelled in-article bar → header toolbar); the header is final. Why the control can't live inside the launch dropdown instead is documented in this comment — verified against the code, with a trigger-only menu entry noted as a possible follow-up. Per-lecture (page-level) enablement is tracked separately in #114.

QuantEcon default: JupyterLite

# myst.yml
project:
  thebe:
    lite: true

Python runs in the browser via Pyodide — no server or Binder to host (avoids the flaky Binder, #26). Verified end-to-end: Power boots the Pyodide kernel in-browser and the Run/Restart/Clear controls appear. The Pyodide package caveat (numba/JAX unavailable) is documented in the README, and binder: / server: backends remain available through the same thebe config for projects that need a full environment.

Changes

  • ComponentComputeToolbarSlot.tsx (new) + the slot <li> in Toolbar.tsx; PageContent.tsx renders the slot instead of the inline NotebookToolbar; styles/app.css #qe-compute-slot rules.
  • Fixtureproject.thebe: { lite: true } in the main visual fixture, plus a second no-thebe fixture (tests/visual/fixture-no-thebe/, served on its own port via a parameterised serve.sh) for the disabled path.
  • Testslive-compute-toggle asserts the Power toggle renders inside the header slot (scoped to #qe-compute-slot, so a regression to the earlier in-article placements fails); live-compute-toggle-absent-without-thebe proves the toggle is gated on the project opting in, not merely on the page being a notebook. Booting Pyodide is a heavy in-browser download, so kernel execution is verified manually, not in CI.
  • Docs — README "Live compute (Thebe / JupyterLite)" section; PLAN.md Phase 2 marked complete; CHANGELOG entry under [Unreleased].

Snapshots

notebook.png and launch-open.png refreshed for both platforms (darwin locally, linux via the /update-snapshots bot — see thread). All checks green on the current head.

Closes #88.

🤖 Generated with Claude Code

Enable opt-in in-page execution via the standard MyST `thebe` frontmatter.
The infra was already wired (ComputeOptionsProvider/ThebeLoaderAndServer in
Page.tsx; ExecuteScopeProvider/NotebookToolbar in PageContent.tsx) and gated
on `project.thebe`; setting it surfaces the @myst-theme/jupyter NotebookToolbar
(Power -> Run/Restart/Clear once a kernel connects) on notebook pages, which is
the per-notebook live-compute toggle — no extra top-bar control, consistent
with the framework. Enablement is config-derived
(`compute.enabled = !!thebeFrontmatterToOptions(project.thebe)`).

QuantEcon default is JupyterLite (`thebe: { lite: true }`): Python runs in the
browser via Pyodide — no server/Binder (avoids the flaky Binder, #26). Verified
end-to-end: clicking Power boots the Pyodide kernel in-browser.

- fixture: set `project.thebe: { lite: true }` so the toolbar is exercised.
- test: assert the live-compute Power toggle renders on /notebook; refresh the
  notebook.png + launch-open.png darwin snapshots (the toolbar now renders on
  the notebook page and behind the launch popover).
- docs: README "Live compute" section incl. the Pyodide package caveat.

Completes Phase 2 (launch parity). Part of #88.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mmcky

mmcky commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

/update-snapshots

Refreshing the linux Chromium baselines for the new live-compute toolbar (notebook.png) and the launch popover backdrop (launch-open.png). Darwin baselines are already updated in db64a28.

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://QuantEcon.github.io/quantecon-theme.mystmd/pr-preview/pr-98/

Built to branch gh-pages at 2026-08-06 06:22 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions

Copy link
Copy Markdown
Contributor

🎭 Refreshed visual baselines in 22e3a63:

  • tests/visual/snapshots/desktop-chrome-linux/launch-open.png
  • tests/visual/snapshots/desktop-chrome-linux/notebook.png
  • tests/visual/snapshots/mobile-chrome-linux/notebook.png

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

🎭 Visual regression results

passed  15 passed
skipped  3 skipped

Details

stats  18 tests across 1 suite
duration  31.9 seconds
commit  3ada514

Skipped tests

mobile-chrome › theme.spec.ts › QuantEcon theme — visual regression › launch-colab
mobile-chrome › theme.spec.ts › QuantEcon theme — visual regression › live-compute-toggle
mobile-chrome › theme.spec.ts › QuantEcon theme — visual regression › live-compute-toggle-absent-without-thebe

… trigger CI)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI 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.

Pull request overview

Enables opt-in in-page live compute in the QuantEcon MyST theme by exercising the existing Thebe/Jupyter integration via project.thebe configuration (defaulting the visual fixture to JupyterLite), plus adds a lightweight UI assertion and documentation updates to reflect Phase 2 completion.

Changes:

  • Update the visual fixture to set project.thebe: { lite: true } (JupyterLite/Pyodide).
  • Add a Playwright assertion that the live-compute “Power” toggle renders on /notebook.
  • Document the feature in README.md and mark Phase 2 complete in PLAN.md.

Reviewed changes

Copilot reviewed 4 out of 10 changed files in this pull request and generated 3 comments.

File Description
tests/visual/theme.spec.ts Adds a new live-compute-toggle test asserting the compute toggle UI is visible.
tests/visual/fixture/myst.yml.in Enables Thebe/JupyterLite in the visual fixture via project.thebe.lite.
README.md Documents how to enable live compute (Thebe/JupyterLite) and notes Pyodide limitations.
PLAN.md Updates Phase 2 status/description to reflect Thebe live compute completion and BinderHub being dropped.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/visual/theme.spec.ts
Comment thread README.md Outdated
Comment thread PLAN.md Outdated
…opilot #98)

- README/PLAN: reword "thebe frontmatter" -> `project.thebe` in `myst.yml`
  (project-level config read from the project manifest, not per-page frontmatter).
- test: match the live-compute toggle by /start compute/i regex, resilient to
  upstream label wording/casing changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mmcky
mmcky marked this pull request as ready for review June 19, 2026 03:40
The native @myst-theme/jupyter NotebookToolbar renders as a right-aligned
floating sticky pill, which looked orphaned on the notebook page. Wrap it in a
labelled "JupyterLite · live compute" bar at the top of the article and
neutralise the floating/sticky/right-aligned layout in app.css, so the Power
toggle (and Run/Restart/Clear once connected) group together as one intentional
control.

- PageContent.tsx: labelled bar wrapper around NotebookToolbar.
- styles/app.css: neutralise `.myst-jp-nb-toolbar` float/sticky/justify-end
  inside `.qe-compute-bar`.
- refresh notebook.png + launch-open.png darwin snapshots.
- README/PLAN: describe the labelled bar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mmcky

mmcky commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

/update-snapshots

Relocated the live-compute controls into a labelled "JupyterLite · live compute" bar (was a floating right-aligned pill), so refreshing the linux notebook.png + launch-open.png baselines. Darwin baselines updated in a646e73.

@github-actions

Copy link
Copy Markdown
Contributor

🎭 Refreshed visual baselines in 0b6c752:

  • tests/visual/snapshots/desktop-chrome-linux/launch-open.png
  • tests/visual/snapshots/desktop-chrome-linux/notebook.png
  • tests/visual/snapshots/mobile-chrome-linux/notebook.png

mmcky and others added 2 commits June 19, 2026 13:59
… trigger CI)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per design feedback, the live-compute control now lives in the fixed header
toolbar (next to Launch) instead of a floating pill / in-article bar, so it is
always visible while scrolling and clusters with the other run controls.

The @myst-theme/jupyter NotebookToolbar is article-coupled (its Power button
calls both useThebeServer.connect() and useExecutionScope.start(slug)), and the
header is mounted outside the Thebe providers — so a naive header button can't
reach the connect hooks. Solved with a React portal: ComputeToolbarSlot
(rendered from PageContent, inside the providers) portals the toolbar's DOM into
a #qe-compute-slot in Toolbar.tsx. React context flows through the React tree,
so its hooks/connection keep working without lifting the provider tree.

- ComputeToolbarSlot.tsx: client-only createPortal of NotebookToolbar.
- Toolbar.tsx: empty:hidden #qe-compute-slot <li> next to Launch.
- styles/app.css: neutralise the floating pill, match 20px header icons.
- desktop-only (like Launch); live-compute-toggle test skips mobile-chrome.
- refresh notebook.png + launch-open.png darwin snapshots; README/PLAN updated.

Verified end-to-end: Power in the header boots the Pyodide kernel; Run/Restart/
Clear then appear inline in the header.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mmcky

mmcky commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

/update-snapshots

Moved the live-compute toggle into the header toolbar (next to Launch) via a portal, refreshing the linux notebook.png + launch-open.png baselines. Darwin baselines updated in 1f5fcfe.

@github-actions

Copy link
Copy Markdown
Contributor

🎭 Refreshed visual baselines in 0037be9:

  • tests/visual/snapshots/desktop-chrome-linux/launch-open.png
  • tests/visual/snapshots/desktop-chrome-linux/notebook.png
  • tests/visual/snapshots/mobile-chrome-linux/notebook.png

mmcky and others added 2 commits June 19, 2026 14:31
… trigger CI)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a second, minimal visual fixture (tests/visual/fixture-no-thebe/) that does
NOT set `project.thebe`, served on a second port, and assert that the
live-compute toggle is absent on a real notebook page there. This covers the
`!compute.enabled` branch of the gate — the toggle only appears when a project
opts into Thebe, not merely because a page is a notebook.

- serve.sh: FIXTURE_DIR env selects the fixture project (default `fixture`).
- playwright.config.ts: second webServer (NO_THEBE_PORT, default 3112).
- theme.spec.ts: live-compute-toggle-absent-without-thebe (desktop-only),
  with a "Notebook outputs" sanity check so count-0 can't false-pass.
- DOM-count assertion, so no new snapshots.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mmcky

mmcky commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

@DrDrij this is plugging in a power button into the title bar when thebe is enabled. I like the integration. I didn't wire it into our play button as the two will almost never been on a the same time, and it was more technically difficult re: dom to do so (as I understand it).

Screenshot 2026-06-19 at 8 44 07 pm

The power button enables thebe connections to the in page code and let's them run via jupyterlite kernel.

@mmcky
mmcky requested a review from DrDrij June 26, 2026 04:17
# Conflicts:
#	tests/visual/fixture/myst.yml.in
@mmcky

mmcky commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@DrDrij friendly nudge on this one — it's the last thing holding up Phase 2.

The question from my comment above: when thebe is enabled, a Power button appears in the title bar next to Launch, and toggling it boots a JupyterLite (Pyodide) kernel so the page's code cells become runnable in place. I deliberately did not fold it into the existing play/Launch control — the two are almost never useful at the same time, and wiring them together was awkward in the DOM.

Just after your read on the placement. If it looks right I'll merge as-is; if you'd rather it sat somewhere else, happy to move it.

For context, the PR is rebased on current main and all checks are green, so it's ready to go whenever you've had a look.

@mmcky

mmcky commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Some context for the placement sign-off: the question of whether this toggle could live inside the launch dropdown instead of the header came up again, so we re-verified the June judgement against the actual code (component tree, the compiled @myst-theme/jupyter package, and Radix behaviour) rather than relying on memory. Summary of what the code says:

Why the control can't live in the dropdown:

  • The Thebe providers mount below the header in the React tree, so nothing in the toolbar (including the launch panel) can call the compute hooks directly — useExecutionScope throws outside the providers. That's the original "DOM/context" difficulty, confirmed.
  • The launch panel is a Radix popover whose content is unmounted whenever it's closed (no forceMount). A live kernel's boot spinner and Run/Restart/Clear would vanish every time the popover shut — the opposite of this PR's design goal of controls that stay visible while scrolling. Notably the kernel itself would survive (session state lives in the providers, the toolbar is stateless) — you'd have a running kernel with no visible status or controls anywhere, which is arguably worse than losing it.
  • The portal trick this PR uses for the header doesn't transfer: the slot lookup runs once at mount, when closed popover content doesn't exist in the DOM, and LaunchButton mounts twice (desktop toolbar + mobile menu) so an in-panel portal target would be ambiguous.

What would be feasible later, if discoverability turns out to be a problem: a trigger-only "Run in browser (JupyterLite)" entry in the launch menu that fires an event to a small booter component inside the providers, with the spinner and Run/Restart/Clear still appearing in the header slot. All the required hooks are exported, so it's moderate work rather than a rebuild — but it adds coordination machinery (idempotency guard, separate enablement plumbing, splitting the panel's github-based disable logic, a mobile gap since the header slot is desktop-only) for what is purely a discoverability win. Deliberately not in this PR.

So the header placement stands as the one home that keeps a live kernel's status permanently visible. Related follow-up: #114 tracks per-lecture enablement (the Power button only on lectures certified Pyodide-compatible) — page-level thebe: frontmatter turns out not to exist in the MyST schema, so that will ride the per-page site: passthrough with a theme-side gate.

…t iterations

A pre-merge audit (three lenses over the diff vs main, findings
adversarially verified) confirmed the code carries no residue from the two
abandoned placements — the app.css block maps 1:1 to shipped upstream
classes, no orphan ids/selectors — but surfaced tidy items around the edges:

- CHANGELOG: the feature had NO [Unreleased] entry, and releases build
  their notes from this file. Added, describing the final header-portal
  design.
- theme.spec.ts: the presence test asserted the toggle existed *anywhere*,
  which couldn't distinguish the final header placement from the earlier
  in-article iterations — now scoped to #qe-compute-slot. Fixed the skip
  reason copied from launch-colab ("mobile uses MobileActionsMenu" — the
  mobile menu has no compute entry; the toggle is desktop-only).
- playwright.config.ts: the no-thebe port default (3112) was duplicated in
  the config and the spec, kept in sync only by convention — the config now
  writes the resolved value back to process.env as the single source of
  truth. Also guard PORT === NO_THEBE_PORT: with reuseExistingServer, the
  second webServer entry would silently reuse the thebe-enabled server and
  the absent-toggle test would assert against the wrong fixture.
- PLAN.md: repaired a comma-spliced, unwrapped line left where the three
  placement rewrites collided.
- fixture-no-thebe/myst.yml.in: don't propagate the retired
  quantecon-theme-src name into a new file.

No rendering changes; snapshots untouched. Typecheck, full visual suite
(15) and FOUC guard (2) pass locally.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 2 — Launch parity (Thebe + config generalisation)

2 participants