Simplify and tidy README; de-emphasize mip
Cut the README from ~200 to ~125 lines: condense the deployment
deep-dives, tighten limitations, and collapse the version notes in
Development. Remove mip from the feature list and walkthrough, fix a
broken reference to a nonexistent demo/content/statsutils.m (now
fib.m), and drop the stale claim that package.json points numbl at a
tarball.
1 changed file+79−154
README.mdmodified+79−154View file
@@ -1,51 +1,42 @@
11 # jupyterlite-numbl-kernel
22
3-Run [**numbl**](https://numbl.org), numerical computing with **MATLAB
4-syntax**, in [JupyterLite](https://jupyterlite.readthedocs.io/) notebooks,
5-**entirely in the browser**, with no server, no kernel process, and nothing
6-for the reader to install.
3+Run [**numbl**](https://numbl.org) — numerical computing with **MATLAB
4+syntax** — in [JupyterLite](https://jupyterlite.readthedocs.io/) notebooks,
5+**entirely in the browser**. No server, no kernel process, nothing for the
6+reader to install.
77
88 numbl is an open-source numerical-computing engine, written in TypeScript,
9-that uses MATLAB syntax, so `.m` code runs unchanged. This kernel runs a
10-numbl session in a Web Worker in the page: variables persist across cells,
11-console output streams into the running cell, plots render as figures in cell
12-outputs (including interactive 3-D), the `mip` package manager can install
13-numbl packages from GitHub, and `.m` files next to the notebook are part of
14-the workspace (named functions, called from cells). Everything runs
15-client-side.
16-
17-**Demo site:**
18-<https://concept-collection.github.io/jupyterlite-numbl-kernel/> (deployed
19-from this repo via GitHub Pages; see `.github/workflows/deploy.yml`)
9+that uses MATLAB syntax, so `.m` code runs unchanged. This kernel runs a numbl
10+session in a Web Worker on the page: variables persist across cells, console
11+output streams into the running cell, plots render as figures (including
12+interactive 3-D), and `.m` files next to the notebook are part of the
13+workspace (named functions, called from cells). Everything runs client-side.
14+
15+**Demo:** <https://concept-collection.github.io/jupyterlite-numbl-kernel/>
16+(deployed from this repo via GitHub Pages)
2017
2118 ## Why
2219
23-This kernel is a proof of concept that a numbl notebook can be a static web
24-page: hostable on GitHub Pages, shareable as a link, and executable by anyone
25-with a browser. Because numbl uses MATLAB syntax and needs no MATLAB/Octave
26-install (or any server process), the same `.m` code that would otherwise
27-require a licensed product behind a server just runs in the tab.
20+A numbl notebook can be a static web page: hostable on GitHub Pages, shareable
21+as a link, and runnable by anyone with a browser. Because numbl uses MATLAB
22+syntax and needs no MATLAB/Octave install (or any server), the same `.m` code
23+that would otherwise sit behind a licensed product just runs in the tab.
2824
2925 ## How it works
3026
31-Three small pieces, all in this repo:
32-
33-- **Kernel** (`src/kernel.ts`): implements JupyterLite's `BaseKernel` from
34- `@jupyterlite/services`. Before each `execute_request`, `.m` files in the
35- notebook's directory (read via the JupyterLite contents manager) are
36- synced into the numbl session; the cell source then runs against the
37- session's persistent workspace (`createNumblSession` /
38- `session.execute` from `numbl/browser`, a Web Worker that numbl manages).
39- Output streams back as `stream` messages; the run's plot instructions are
40- published as `display_data` with the mime type
41- `application/vnd.numbl.figure+json`.
42-- **Figure renderer** (`src/mime.tsx`): a JupyterLab mime renderer for that
43- mime type: it replays the instructions through numbl's figures reducer and
44- mounts numbl's React `FigureView` (from `numbl/graphics`). Outputs are
45- plain JSON, so saved notebooks re-render wherever the extension is
46- installed.
47-- **Kernel registration** (`src/index.ts`): registers the kernelspec with
48- JupyterLite's `IKernelSpecs`.
27+Three small pieces, all in `src/`:
28+
29+- **Kernel** ([kernel.ts](src/kernel.ts)) implements JupyterLite's
30+ `BaseKernel`. Before each cell runs, `.m` files in the notebook's directory
31+ are synced into the numbl session; the cell then runs against the session's
32+ persistent workspace (a Web Worker that numbl manages). Console output
33+ streams back as `stream` messages, and plots are published as `display_data`
34+ with the mime type `application/vnd.numbl.figure+json`.
35+- **Figure renderer** ([mime.tsx](src/mime.tsx)) is a mime renderer for that
36+ type: it replays the plot instructions and mounts numbl's React `FigureView`.
37+ Outputs are plain JSON, so saved notebooks re-render wherever the extension
38+ is installed.
39+- **Registration** ([index.ts](src/index.ts)) registers the kernelspec.
4940
5041 ## Build a site with it
5142
@@ -55,122 +46,60 @@ jupyter lite build --contents my-notebooks --output-dir dist
5546 # dist/ is a static site; serve it anywhere
5647 ```
5748
58-The `demo/` directory in this repo contains the demo site sources
59-(notebooks + requirements); `.github/workflows/deploy.yml` builds and
60-deploys it to GitHub Pages. `demo/content/` is a numbered walkthrough: an
61-intro (with plotting), a systematic language tour (data types, matrices,
62-control flow, linear algebra, data structures, numerical methods,
63-plotting), the advanced MATLAB object model (classes and OOP, namespaces
64-and packages), and finally installing packages with `mip`. The class and
65-package `.m` files live next to the notebooks (e.g. `Vec2.m`, `+geom/`,
66-`@Poly/`) and are synced into the session recursively.
67-
68-### Always-fresh content (demo choice)
69-
70-JupyterLite caches content in two layers that both defeat redeploys:
71-
72-1. It copies notebooks into the browser's IndexedDB on first visit, and that
73- local copy then wins over the deployed files **even after a redeploy**.
74-2. Its **service worker** is an offline caching proxy for the app itself and
75- for content files, so it can keep serving the old app and old notebooks
76- after a redeploy until it happens to update.
77-
78-Since this is a demo, `demo/jupyter-lite.json` neutralizes both: it uses
79-JupyterLite's in-memory storage (so every reload re-seeds the latest
80-deployed notebooks) and disables the service-worker plugin (which the numbl
81-kernel doesn't need, since it reads content on the main thread, not via the
82-service worker's kernel drive):
83-
84-```json
85-{
86- "jupyter-config-data": {
87- "enableMemoryStorage": true,
88- "contentsStorageDrivers": ["memoryStorageDriver"],
89- "settingsStorageDrivers": ["memoryStorageDriver"],
90- "workspacesStorageDrivers": ["memoryStorageDriver"],
91- "disabledExtensions": [
92- "@jupyterlite/application-extension:service-worker-manager"
93- ]
94- }
95-}
96-```
49+`.m` files next to a notebook are synced into the session recursively, so
50+MATLAB's folder-based `+namespace/`, `@class/`, and `private/` layouts work as
51+expected.
52+
53+The `demo/` directory is the source of the demo site above — a numbered
54+walkthrough of the language and the MATLAB object model (classes, namespaces,
55+packages). [`.github/workflows/deploy.yml`](.github/workflows/deploy.yml)
56+builds and deploys it to GitHub Pages.
57+
58+### Fresh content on redeploy (demo choice)
59+
60+JupyterLite caches notebooks in the browser (IndexedDB) and can keep serving
61+stale copies from its service worker even after a redeploy. Because this is a
62+demo, [`demo/jupyter-lite.json`](demo/jupyter-lite.json) uses in-memory
63+storage and disables the service worker, so every reload re-seeds the latest
64+deployed notebooks. The trade-off is that a visitor's edits last only for the
65+session. For a real deployment where users keep their work, drop those
66+settings and use JupyterLite's default persistent storage.
67+
68+### Cross-origin isolation (for interrupt and input)
9769
98-The trade-off is that a visitor's edits live only for the session and are
99-discarded on reload. A visitor who loaded the site **before** the service
100-worker was disabled still has it registered and must clear browser data
101-(or `Help > Clear Browser Data`) once to get past it. For a real deployment where users should keep their
102-work, omit these keys (the default persistent storage) and bump
103-`contentsStorageName` when you want to force-refresh shipped content.
104-numbl's own package cache (installed via `mip`) lives in a separate
105-IndexedDB store and is unaffected, so `mip`-installed packages still
106-persist across reloads.
107-
108-### Cross-origin isolation (for interrupt)
109-
110-Cell interrupt needs a `SharedArrayBuffer`, which browsers only expose on a
111-**cross-origin-isolated** page (served with `Cross-Origin-Opener-Policy:
112-same-origin` and `Cross-Origin-Embedder-Policy: credentialless`). GitHub Pages
113-serves static files and can't set those headers, so the demo synthesizes them
114-client-side with a small service worker, `demo/coi-serviceworker.js` (based on
115-[coi-serviceworker](https://github.com/niccokunzmann/coi-serviceworker)). It
116-only rewrites **same-origin** responses, so numbl's cross-origin `mip` download
117-from its GitHub release still works.
118-
119-`demo/inject-coi.mjs` runs after `jupyter lite build`: it copies the worker to
120-the site root and adds a `<script>` registering it to every generated page's
121-`<head>` (the deploy workflow does this automatically). The worker adds no
122-caching — it only injects headers — so it doesn't undermine the always-fresh
123-content choice above. Service workers require a secure context, so view the
124-site over `https://` or `http://localhost` / `http://127.0.0.1`; an `http://`
125-LAN IP or an embedded/preview browser has no service worker, and interrupt
126-degrades to a no-op there.
70+The Stop button and `input()` both rely on a `SharedArrayBuffer`, which
71+browsers expose only on a **cross-origin-isolated** page (`COOP`/`COEP`
72+headers). GitHub Pages can't set those headers, so the demo ships a small
73+service worker, [`demo/coi-serviceworker.js`](demo/coi-serviceworker.js)
74+(based on [coi-serviceworker](https://github.com/niccokunzmann/coi-serviceworker)),
75+that synthesizes them client-side; [`demo/inject-coi.mjs`](demo/inject-coi.mjs)
76+wires it into every generated page during the build. Without isolation,
77+interrupt and `input()` degrade gracefully (see Limitations).
12778
12879 ## Limitations (proof of concept)
12980
130-- **Interrupt** works cooperatively: the Stop button aborts the running cell
131- at the next loop iteration or function/builtin call, reports it as a
132- `KeyboardInterrupt`, and leaves the workspace intact (variables from before
133- the cell survive). It relies on a `SharedArrayBuffer` cancel flag that numbl
134- polls during execution, so the page must be **cross-origin isolated**
135- (`COOP`/`COEP`). Plain GitHub Pages can't set those headers, so the demo
136- ships a `coi-serviceworker` that synthesizes them (see [Cross-origin
137- isolation](#cross-origin-isolation-for-interrupt)). Two caveats: on a
138- deployment that is **not** cross-origin isolated the interrupt silently
139- falls back to a no-op (a runaway cell can then only be stopped by
140- restarting the kernel), and a **tight loop with no function or builtin
141- calls** (e.g. `while true; x = x + 1; end`) is JIT-compiled straight
142- through with no cancellation checkpoint, so it too needs a restart.
143-- **`input()`** (stdin) works: a cell that calls `input()` prompts in the
144- notebook and blocks until you answer, then resumes with your entry —
145- numeric (`n = input('n? ')`) or, with `input(prompt, 's')`, a string. Like
146- interrupt it uses a `SharedArrayBuffer`, so it needs the page to be
147- cross-origin isolated; on a non-isolated deployment `input()` raises
148- ("input() is not available in this environment") rather than prompting.
149-- **Figures are per-cell** (like inline matplotlib): each cell renders the
150- figures its own commands produce; `hold on` does not span cells.
151-- **Named function definitions are not supported inside cells** (a numbl
152- REPL limitation): anonymous functions work, and named functions belong in
153- `.m` files next to the notebook (see `demo/content/statsutils.m`), which
154- this kernel syncs into the session automatically.
155-- The `.m`-file sync is **one-way**: deleting a `.m` file from the file
156- browser leaves its function defined until the kernel restarts, and files
157- written by cell code (e.g. via `fopen`) don't appear back in the file
158- browser.
159-- **uihtml** components render display-only; the MATLAB↔HTML event bridge
160- is not wired into outputs yet.
161-- numbl itself is not MATLAB: it covers a large, tested subset of the
162- language and toolbox surface. See [numbl](https://numbl.org) for scope.
81+- **Interrupt** is cooperative: the Stop button aborts the running cell at the
82+ next loop iteration or function call, reports a `KeyboardInterrupt`, and
83+ leaves earlier variables intact. It needs cross-origin isolation (above); on
84+ a non-isolated page it's a no-op, and a tight loop with no calls (e.g.
85+ `while true; x = x + 1; end`) has no checkpoint to abort at — both cases need
86+ a kernel restart.
87+- **`input()`** prompts in the notebook and blocks until you answer. It also
88+ needs cross-origin isolation; without it, `input()` raises rather than
89+ prompting.
90+- **Figures are per-cell** (like inline matplotlib): `hold on` does not span
91+ cells.
92+- **Named functions go in `.m` files**, not cells (a numbl REPL limitation);
93+ anonymous functions work in cells. See [`demo/content/fib.m`](demo/content/fib.m).
94+- **`.m` sync is one-way**: deleting a file leaves its function defined until
95+ the kernel restarts, and files written by cell code don't appear back in the
96+ file browser.
97+- **numbl is not MATLAB** — it covers a large, tested subset of the language
98+ and toolboxes. See [numbl](https://numbl.org) for scope.
16399
164100 ## Development
165101
166-Requires Python ≥ 3.9 and NodeJS ≥ 20, and `numbl >= 0.4.16` on npm — the
167-release with the browser stdin API (`onInputRequest` / `provideInput` /
168-`canInput`) behind `input()`; it also carries the `session.interrupt()` /
169-`canInterrupt` cancellation API for cell interrupt (added in 0.4.15) and the
170-incremental `session.execute` browser API this kernel is built on (0.4.14).
171-To develop against an unreleased numbl checkout, run `npm pack` there and
172-point the `numbl` dependency at the tarball (as `package.json` does while
173-0.4.16 is being published).
102+Requires Python ≥ 3.9, Node ≥ 20, and `numbl >= 0.4.16`.
174103
175104 ```bash
176105 python -m venv .venv && source .venv/bin/activate
@@ -184,9 +113,7 @@ pip install -e . # editable install, registers the labextension
184113 pip install -r demo/requirements.txt
185114 jupyter lite build --lite-dir demo --contents content --output-dir demo/_output
186115 node demo/inject-coi.mjs demo/_output # cross-origin isolation, for interrupt
187-python -m http.server -d demo/_output 8000
188-# then open http://localhost:8000 — a secure context, required for the
189-# service worker (interrupt is a no-op without it)
116+python -m http.server -d demo/_output 8000 # then open http://localhost:8000
190117 ```
191118
192119 `jlpm watch` rebuilds on change during development.
@@ -194,7 +121,5 @@ python -m http.server -d demo/_output 8000
194121 ## License
195122
196123 Apache-2.0. Built on [numbl](https://numbl.org) and
197-the [JupyterLite](https://github.com/jupyterlite/jupyterlite) kernel API;
198-scaffolding follows the
199-[jupyterlite/echo-kernel](https://github.com/jupyterlite/echo-kernel)
200-template.
124+[JupyterLite](https://github.com/jupyterlite/jupyterlite); scaffolded from the
125+[jupyterlite/echo-kernel](https://github.com/jupyterlite/echo-kernel) template.