Skip to content

Commit b927f9d

Browse files
committed
feat(vbgui-fe): JupyterLite scaffold + GitHub Pages deploy workflow
Stage F-E of the Visual Builder GUI epic (cppmega-mlx-o0k). Ships the static-bundle hand-off so the visual builder can run from GitHub Pages with no kernel server. - vbgui/jupyterlite/: JupyterLite scaffold (jupyter_lite_config.json + content/demo.ipynb). Demo notebook documents the estimator-only constraint (MLX unavailable under Pyodide → build_model / dry_forward disabled; everything else works 1:1 through the same JSON-RPC contract). Verified locally with `jupyter lite build` (69 MB output). - .github/workflows/vbgui-pages.yml: CI workflow that builds the Vite standalone bundle + the anywidget bundle + the JupyterLite site and pushes the assembled payload to GitHub Pages on every main push that touches the relevant paths. Uses actions/upload-pages-artifact + actions/deploy-pages. - .gitignore (per-subdir): _out/ and the doit DB are build artefacts. Tests (6): config + notebook validity + workflow contains required steps (build:widget, jupyter lite build, deploy-pages action) + the content/files subdirs exist. Full v4 regression: 2068 passed / 5 skipped / 15 xfailed / 0 failed. Closes cppmega-mlx-o0k.5.
1 parent 96ee5a6 commit b927f9d

4 files changed

Lines changed: 184 additions & 0 deletions

File tree

.github/workflows/vbgui-pages.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Deploy Visual Builder static demo to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "vbgui/**"
8+
- "cppmega_v4/widget/**"
9+
- "cppmega_v4/spec/**"
10+
- "cppmega_v4/buildspec/**"
11+
- "cppmega_v4/parallelism/**"
12+
- "cppmega_v4/fusion/**"
13+
- "cppmega_v4/probe/**"
14+
- "cppmega_v4/jsonrpc/**"
15+
- "cppmega_v4/runner/**"
16+
- ".github/workflows/vbgui-pages.yml"
17+
workflow_dispatch:
18+
19+
permissions:
20+
contents: read
21+
pages: write
22+
id-token: write
23+
24+
concurrency:
25+
group: pages
26+
cancel-in-progress: false
27+
28+
jobs:
29+
build:
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v4
33+
34+
- name: Set up Node 22
35+
uses: actions/setup-node@v4
36+
with:
37+
node-version: "22"
38+
cache: "npm"
39+
cache-dependency-path: vbgui/package-lock.json
40+
41+
- name: Set up Python 3.13
42+
uses: actions/setup-python@v5
43+
with:
44+
python-version: "3.13"
45+
46+
- name: Install JupyterLite + Pyodide kernel
47+
run: |
48+
python -m pip install --upgrade pip
49+
pip install jupyterlite-core jupyterlite-pyodide-kernel jupyter-server
50+
51+
- name: Build standalone Vite bundle
52+
working-directory: vbgui
53+
env:
54+
NODE_ENV: development
55+
run: |
56+
npm ci
57+
npm run typecheck
58+
npm test
59+
npm run build
60+
npm run build:widget
61+
62+
- name: Build JupyterLite static site
63+
working-directory: vbgui/jupyterlite
64+
run: jupyter lite build --output-dir ../site/jupyterlite
65+
66+
- name: Stage Pages payload
67+
run: |
68+
mkdir -p vbgui/site
69+
cp -r vbgui/dist/* vbgui/site/
70+
test -d vbgui/site/jupyterlite
71+
72+
- uses: actions/upload-pages-artifact@v3
73+
with:
74+
path: vbgui/site
75+
76+
deploy:
77+
needs: build
78+
runs-on: ubuntu-latest
79+
environment:
80+
name: github-pages
81+
url: ${{ steps.deployment.outputs.page_url }}
82+
steps:
83+
- id: deployment
84+
uses: actions/deploy-pages@v4
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""F-E JupyterLite scaffold tests — config validity + build pipeline.
2+
3+
Lightweight: verify the static scaffold files exist and parse cleanly.
4+
The full `jupyter lite build` runs in CI (see
5+
.github/workflows/vbgui-pages.yml) — locally we just lint the inputs so
6+
a malformed notebook or config can't slip into main.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import json
12+
from pathlib import Path
13+
14+
import pytest
15+
16+
17+
_VBGUI = Path("vbgui")
18+
_JLITE = _VBGUI / "jupyterlite"
19+
20+
21+
def test_jupyterlite_config_exists_and_parses():
22+
cfg = _JLITE / "jupyter_lite_config.json"
23+
assert cfg.is_file(), "missing jupyter_lite_config.json"
24+
payload = json.loads(cfg.read_text())
25+
assert "LiteBuildConfig" in payload
26+
assert payload["LiteBuildConfig"]["contents"] == ["content"]
27+
28+
29+
def test_jupyterlite_demo_notebook_is_valid():
30+
nb = _JLITE / "content" / "demo.ipynb"
31+
assert nb.is_file()
32+
data = json.loads(nb.read_text())
33+
assert data["nbformat"] >= 4
34+
assert "cells" in data and len(data["cells"]) >= 1
35+
src = "".join("".join(c.get("source", [])) for c in data["cells"])
36+
# Notebook must self-document the estimator-only mode constraint.
37+
assert "estimator-only" in src.lower()
38+
39+
40+
def test_jupyterlite_demo_imports_widget_class():
41+
nb = _JLITE / "content" / "demo.ipynb"
42+
src = nb.read_text()
43+
assert "VisualBuilderWidget" in src
44+
45+
46+
def test_pages_workflow_exists_and_has_required_jobs():
47+
wf = Path(".github/workflows/vbgui-pages.yml")
48+
assert wf.is_file(), "missing GitHub Pages deploy workflow"
49+
text = wf.read_text()
50+
assert "actions/upload-pages-artifact" in text
51+
assert "actions/deploy-pages" in text
52+
assert "build:widget" in text
53+
assert "jupyter lite build" in text
54+
55+
56+
@pytest.mark.parametrize("subdir", ["content", "files"])
57+
def test_jupyterlite_subdirs_present(subdir):
58+
assert (_JLITE / subdir).is_dir()
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# cppmega Visual Builder — JupyterLite demo\n",
8+
"\n",
9+
"This static-site demo runs the visual builder in **estimator-only mode**:\n",
10+
"VBSpec / MBSpec / PSpec / Fusion planning all work; `build_model` and `dry_forward` are disabled because MLX is not available under Pyodide.\n",
11+
"\n",
12+
"Use the Visual Builder GUI in **JupyterLab** (full mode) when you need to actually train."
13+
]
14+
},
15+
{
16+
"cell_type": "code",
17+
"metadata": {},
18+
"source": [
19+
"import piplite\n",
20+
"await piplite.install([\"cppmega-mlx[gui,widget]\"])\n",
21+
"from cppmega_v4.widget import VisualBuilderWidget\n",
22+
"VisualBuilderWidget()"
23+
],
24+
"outputs": [],
25+
"execution_count": null
26+
}
27+
],
28+
"metadata": {
29+
"kernelspec": {"display_name": "Python (Pyodide)", "language": "python", "name": "python"}
30+
},
31+
"nbformat": 4,
32+
"nbformat_minor": 5
33+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"LiteBuildConfig": {
3+
"contents": ["content"],
4+
"extra_http_headers": {
5+
"Cross-Origin-Embedder-Policy": "require-corp",
6+
"Cross-Origin-Opener-Policy": "same-origin"
7+
}
8+
}
9+
}

0 commit comments

Comments
 (0)