# jupyterlite-numbl-kernel Run [**numbl**](https://numbl.org), numerical computing with **MATLAB syntax**, in [JupyterLite](https://jupyterlite.readthedocs.io/) notebooks, **entirely in the browser**, with no server, no kernel process, and nothing for the reader to install. numbl is an open-source numerical-computing engine, written in TypeScript, that uses MATLAB syntax, so `.m` code runs unchanged. This kernel runs a numbl session in a Web Worker in the page: variables persist across cells, console output streams into the running cell, plots render as figures in cell outputs (including interactive 3-D), the `mip` package manager can install numbl packages from GitHub, and `.m` files next to the notebook are part of the workspace (named functions, called from cells). Everything runs client-side. **Demo site:** (deployed from this repo via GitHub Pages; see `.github/workflows/deploy.yml`) ## Why This kernel is a proof of concept that a numbl notebook can be a static web page: hostable on GitHub Pages, shareable as a link, and executable by anyone with a browser. Because numbl uses MATLAB syntax and needs no MATLAB/Octave install (or any server process), the same `.m` code that would otherwise require a licensed product behind a server just runs in the tab. ## How it works Three small pieces, all in this repo: - **Kernel** (`src/kernel.ts`): implements JupyterLite's `BaseKernel` from `@jupyterlite/services`. Before each `execute_request`, `.m` files in the notebook's directory (read via the JupyterLite contents manager) are synced into the numbl session; the cell source then runs against the session's persistent workspace (`createNumblSession` / `session.execute` from `numbl/browser`, a Web Worker that numbl manages). Output streams back as `stream` messages; the run's plot instructions are published as `display_data` with the mime type `application/vnd.numbl.figure+json`. - **Figure renderer** (`src/mime.tsx`): a JupyterLab mime renderer for that mime type: it replays the instructions through numbl's figures reducer and mounts numbl's React `FigureView` (from `numbl/graphics`). Outputs are plain JSON, so saved notebooks re-render wherever the extension is installed. - **Kernel registration** (`src/index.ts`): registers the kernelspec with JupyterLite's `IKernelSpecs`. ## Build a site with it ```bash pip install jupyterlite-core jupyterlite-numbl-kernel jupyter lite build --contents my-notebooks --output-dir dist # dist/ is a static site; serve it anywhere ``` The `demo/` directory in this repo contains the demo site sources (notebooks + requirements); `.github/workflows/deploy.yml` builds and deploys it to GitHub Pages. `demo/content/` is a numbered walkthrough: an intro (with plotting), a systematic language tour (data types, matrices, control flow, linear algebra, data structures, numerical methods, plotting), the advanced MATLAB object model (classes and OOP, namespaces and packages), and finally installing packages with `mip`. The class and package `.m` files live next to the notebooks (e.g. `Vec2.m`, `+geom/`, `@Poly/`) and are synced into the session recursively. ### Always-fresh content (demo choice) JupyterLite caches content in two layers that both defeat redeploys: 1. It copies notebooks into the browser's IndexedDB on first visit, and that local copy then wins over the deployed files **even after a redeploy**. 2. Its **service worker** is an offline caching proxy for the app itself and for content files, so it can keep serving the old app and old notebooks after a redeploy until it happens to update. Since this is a demo, `demo/jupyter-lite.json` neutralizes both: it uses JupyterLite's in-memory storage (so every reload re-seeds the latest deployed notebooks) and disables the service-worker plugin (which the numbl kernel doesn't need, since it reads content on the main thread, not via the service worker's kernel drive): ```json { "jupyter-config-data": { "enableMemoryStorage": true, "contentsStorageDrivers": ["memoryStorageDriver"], "settingsStorageDrivers": ["memoryStorageDriver"], "workspacesStorageDrivers": ["memoryStorageDriver"], "disabledExtensions": [ "@jupyterlite/application-extension:service-worker-manager" ] } } ``` The trade-off is that a visitor's edits live only for the session and are discarded on reload. A visitor who loaded the site **before** the service worker was disabled still has it registered and must clear browser data (or `Help > Clear Browser Data`) once to get past it. For a real deployment where users should keep their work, omit these keys (the default persistent storage) and bump `contentsStorageName` when you want to force-refresh shipped content. numbl's own package cache (installed via `mip`) lives in a separate IndexedDB store and is unaffected, so `mip`-installed packages still persist across reloads. ### Cross-origin isolation (for interrupt) Cell interrupt needs a `SharedArrayBuffer`, which browsers only expose on a **cross-origin-isolated** page (served with `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: credentialless`). GitHub Pages serves static files and can't set those headers, so the demo synthesizes them client-side with a small service worker, `demo/coi-serviceworker.js` (based on [coi-serviceworker](https://github.com/niccokunzmann/coi-serviceworker)). It only rewrites **same-origin** responses, so numbl's cross-origin `mip` download from its GitHub release still works. `demo/inject-coi.mjs` runs after `jupyter lite build`: it copies the worker to the site root and adds a `