Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
# / → static welcome page (web/index.html)
# /slides/ → reveal.js findings deck (web/slides/)
# /playground/ → the Vite IDE app (built with a matching base path)
# /jupyterlite/ → JupyterLite in-browser demo (jupyter lite build)
# /sample-package/ → lesson 8's self-hosted Data Package (kept at root
# so the URL hard-coded in lesson 8 stays valid)
# PR previews (see pr-preview.yml) live under `/pr-preview/pr-N/` of the
Expand Down Expand Up @@ -59,7 +60,28 @@ jobs:
VITE_BASE_PATH: /tabular-data-playground/playground/
run: pnpm run build

- name: Assemble published site (welcome + slides + IDE + sample-package)
- name: Set up Python for JupyterLite build
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install JupyterLite build dependencies
working-directory: ${{ github.workspace }}
run: pip install -r jupyterlite/requirements.txt

- name: Build JupyterLite demo (served under /jupyterlite/)
working-directory: ${{ github.workspace }}/jupyterlite
run: |
jupyter lite build \
--output-dir _output \
--base-url /tabular-data-playground/jupyterlite/

- name: Verify JupyterLite build produced output
working-directory: ${{ github.workspace }}/jupyterlite
run: |
test -d _output && test -n "$(ls -A _output)"

- name: Assemble published site (welcome + slides + IDE + jupyterlite + sample-package)
working-directory: ${{ github.workspace }}
run: |
set -e
Expand All @@ -70,6 +92,9 @@ jobs:
# IDE under /playground/
mkdir -p _site/playground
cp -r app/dist/. _site/playground/
# JupyterLite demo under /jupyterlite/
mkdir -p _site/jupyterlite
cp -r jupyterlite/_output/. _site/jupyterlite/
# Keep the lesson-8 sample package at the gh-pages root
if [ -d _site/playground/sample-package ]; then
rm -rf _site/sample-package
Expand Down
26 changes: 25 additions & 1 deletion .github/workflows/pr-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ name: PR Preview to GitHub Pages
# /pr-preview/pr-<N>/ → welcome page (web/index.html)
# /pr-preview/pr-<N>/slides/ → reveal.js findings deck
# /pr-preview/pr-<N>/playground/ → the Vite IDE (PR-scoped base path)
# /pr-preview/pr-<N>/jupyterlite/ → JupyterLite demo (PR-scoped base path)
# /pr-preview/pr-<N>/sample-package/ → lesson 8's package, kept at the
# preview root to mirror production
# A single sticky comment (idempotent via the marker below) links to
Expand Down Expand Up @@ -62,14 +63,36 @@ jobs:
VITE_BASE_PATH: /tabular-data-playground/pr-preview/pr-${{ github.event.pull_request.number }}/playground/
run: pnpm run build

- name: Assemble preview site (welcome + slides + IDE + sample-package)
- name: Set up Python for JupyterLite build
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install JupyterLite build dependencies
run: pip install -r jupyterlite/requirements.txt

- name: Build JupyterLite demo with PR-scoped base path
working-directory: jupyterlite
run: |
jupyter lite build \
--output-dir _output \
--base-url /tabular-data-playground/pr-preview/pr-${{ github.event.pull_request.number }}/jupyterlite/

- name: Verify JupyterLite build produced output
working-directory: jupyterlite
run: |
test -d _output && test -n "$(ls -A _output)"

- name: Assemble preview site (welcome + slides + IDE + jupyterlite + sample-package)
run: |
set -e
rm -rf _site
mkdir -p _site
cp -r web/. _site/
mkdir -p _site/playground
cp -r app/dist/. _site/playground/
mkdir -p _site/jupyterlite
cp -r jupyterlite/_output/. _site/jupyterlite/
if [ -d _site/playground/sample-package ]; then
rm -rf _site/sample-package
mv _site/playground/sample-package _site/sample-package
Expand Down Expand Up @@ -103,6 +126,7 @@ jobs:
`- Welcome page: ${base}`,
`- Findings slides: ${base}slides/`,
`- IDE: ${base}playground/`,
`- JupyterLite demo: ${base}jupyterlite/`,
'',
`Built from \`${sha}\`. Updates on every push to this PR; removed when the PR closes.`,
].join('\n');
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,4 @@ vite.config.ts.timestamp-*
# Claude Code local/user-specific artifacts (skills under .claude/skills/ are tracked)
.claude/settings.local.json
.claude/.credentials.json
.jupyterlite.doit.db
42 changes: 40 additions & 2 deletions .specify/memory/constitution.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,37 @@
<!--
SYNC IMPACT REPORT
Version change: 2.0.0 → 2.1.0
Version change: 2.1.0 → 2.2.0
Bump rationale: Technology Constraints materially expanded (MINOR) — a new
"JupyterLite demo" bullet added to record the JupyterLite-based in-browser
demo sub-site (`jupyterlite/`) and its independent pin set. This is a new
infrastructural dependency (build-time `jupyter lite build` step + its own
`requirements.txt`), which per the section's own rule is an amendment
rather than a plain PR. No principle changed; no Definition of Done change.
Modified principles: none.
Added sections: none (Technology Constraints expanded with new bullet).
Removed sections: none.
Templates requiring updates:
- .specify/templates/plan-template.md — no edit required.
- .specify/templates/spec-template.md — no edit required.
- .specify/templates/tasks-template.md — no edit required.
- .specify/templates/checklist-template.md — no edit required.
Companion edits in same commit:
- README.md — JupyterLite/kernel pins recorded in Pinned versions table.
- docs/limitations.md — JupyterLite demo sharp edges enumerated.
- jupyterlite/ — new demo source directory.
- .github/workflows/deploy.yml — additive JupyterLite build step.
- .github/workflows/pr-preview.yml — additive JupyterLite build step.
- web/index.html — link to /jupyterlite/ demo added.

Previous amendment (2.0.0 → 2.1.0): Technology Constraints "Python runtime"
slot expanded to admit `livemark` (lesson 9 Livemark capstone).
Previous amendment (1.0.0 → 2.0.0): Principle VI redefined from "Pin and
Freeze" to "Pin Versions, Iterate Forward".
Follow-up TODOs: None.
-->

<!--
PREVIOUS SYNC IMPACT REPORT (2.0.0 → 2.1.0)
Bump rationale: Technology Constraints materially expanded (MINOR) — the
"Python runtime" slot now admits `livemark`, the Frictionless publishing
tool used by lesson 9 (Publish with Livemark). It is pinned and installed
Expand Down Expand Up @@ -170,6 +201,13 @@ The v1 stack is fixed by the spec and is part of the constitutional surface
CI smoke-tests Chromium only.
- **Accessibility**: best-effort — keyboard-navigable, semantic HTML, no
formal WCAG conformance commitment (decision #15).
- **JupyterLite demo**: a self-contained in-browser demo sub-site at
`/jupyterlite/` built by `jupyter lite build` (build-time Python step).
Toolchain pins live in `jupyterlite/requirements.txt`; they are
**independent** of the frozen playground's Pyodide `0.27.7` pin.
The demo is published additively alongside `/playground/` and is
online-only by design (Pyodide/wheels fetched from CDN at runtime).
Sharp edges in `docs/limitations.md`.

Substitutions within these slots (e.g. `dockview` instead of `react-mosaic`)
are permitted and not amendments. Adding a new infrastructural dependency,
Expand Down Expand Up @@ -238,4 +276,4 @@ Complexity Tracking section before `/speckit-tasks` runs.
the constitution and the codebase continue to evolve while the project
is in its pre-production research phase.

**Version**: 2.1.0 | **Ratified**: 2026-05-08 | **Last Amended**: 2026-06-10
**Version**: 2.2.0 | **Ratified**: 2026-05-08 | **Last Amended**: 2026-07-04
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ the lessons in `content/lessons/` were authored against.
| Table Schema JSON Schema | snapshot 2026-05-09 | Bundled from `https://specs.frictionlessdata.io/schemas/table-schema.json` to `app/src/editor/schemas/table-schema.json`. |
| Table Dialect JSON Schema | snapshot 2026-05-09 (v2) | Bundled from `https://datapackage.org/profiles/2.0/tabledialect.json` to `app/src/editor/schemas/table-dialect.json` (`specs.frictionlessdata.io` returns 404 for dialect; `datapackage.org` is the canonical home). |
| reveal.js (findings slides) | `5.1.0` | Loaded by `web/slides/index.html` from `https://cdn.jsdelivr.net/npm/reveal.js@5.1.0/` — the URL is the pin. |
| JupyterLite core (demo) | `0.8.0` | `jupyterlite/requirements.txt`; **independent** of the frozen playground's Pyodide pin. |
| JupyterLite Pyodide kernel (demo) | `0.8.1` | `jupyterlite/requirements.txt`; bundled Pyodide version determined by the kernel release. |
| jupyter-server (demo build) | `2.20.0` | `jupyterlite/requirements.txt`; required by `jupyterlite-core` to index and serve custom content files. |
| matplotlib (demo, in-browser) | runtime (Pyodide wheel) | Installed in-browser via `%pip install matplotlib` in the demo notebook; version resolved from the JupyterLite Pyodide kernel's bundled wheel index. |

The editor still attempts a runtime fetch of the canonical schemas
on mount with a short timeout and uses the live copy if it
Expand Down
35 changes: 35 additions & 0 deletions docs/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,3 +480,38 @@ double-Tab listing. xterm.js's default `bellStyle` is silent
This is a deliberate choice (classroom/demo context); enabling
a bell sound would require shipping an audio asset and is out
of scope.

## JupyterLite demo findings

### Demo is online-only (no offline / air-gapped path)

The JupyterLite demo at `/jupyterlite/` relies on CDN-hosted
Pyodide and wheel packages fetched at runtime by the
JupyterLite Pyodide kernel. A visitor without internet access
will see the notebook interface but cells that trigger
`%pip install` or import a non-bundled package will fail
silently or with a network error. There is no offline fallback
and no service-worker caching of the wheel files. This is
deliberate — bundling the full Pyodide + matplotlib wheel set
into the repo would add tens of MB of binary content.

### Cold-load wheel weight (first uncached visit)

The first uncached visit fetches the Pyodide runtime (~10 MB
compressed) plus the matplotlib wheel and its transitive
dependencies from the Pyodide CDN. Subsequent visits are
browser-cached. There is no hard latency budget; on a slow
connection the first `Run All` on the demo notebook can take
tens of seconds before the figure appears. This is an
accepted trade-off of the CDN-delivery model.

### Separate pin set — independent of the frozen playground

The demo (`jupyterlite/requirements.txt`) carries its own
`jupyterlite-core` and `jupyterlite-pyodide-kernel` pins,
decoupled from the frozen playground's Pyodide `0.27.7`.
Bumping either set does not affect the other, but they must
be kept consistent within each context: the playground's
Frictionless lessons were authored against Pyodide `0.27.7`
and bumping that pin risks lesson regression. The demo pins
are independent and can be bumped separately.
4 changes: 4 additions & 0 deletions jupyterlite/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
_output/
__pycache__/
*.pyc
.jupyterlite.doit.db
82 changes: 82 additions & 0 deletions jupyterlite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# JupyterLite Demo

A self-contained in-browser JupyterLite site that ships one example notebook
(`demo.ipynb`) loading a sample CSV and rendering matplotlib figures inline.
Everything runs in the browser via Pyodide — no server, no account required.

Published at `/jupyterlite/` on the GitHub Pages site alongside the frozen
playground (`/playground/`). The two surfaces are built by independent
toolchains and share no pins.

## Requirements

Python 3.11+ (build-time only). The notebook runs against the JupyterLite
Pyodide kernel's bundled Python in-browser; no local Python is used at
runtime.

## Single reproducible build command

From the **`jupyterlite/` directory**:

```bash
# From the repository root:
pip install -r jupyterlite/requirements.txt
cd jupyterlite
jupyter lite build --output-dir _output
```

The output is written to `jupyterlite/_output/`. Serve locally with:

```bash
# From jupyterlite/:
jupyter lite serve --output-dir _output
```

Then open `http://localhost:8000` (or the URL printed in the terminal),
navigate to `demo.ipynb`, and run all cells — a matplotlib figure should
render inline.

## Base URL parameterisation

For deployment under a sub-path, pass `--base-url` to the build command
(run from the `jupyterlite/` directory):

```bash
jupyter lite build \
--output-dir _output \
--base-url /tabular-data-playground/jupyterlite/
```

The CI workflows set the correct base URL for production
(`/tabular-data-playground/jupyterlite/`) and for PR previews
(`/tabular-data-playground/pr-preview/pr-<N>/jupyterlite/`).

## Pinned toolchain

Versions are pinned in `requirements.txt`. Do **not** bump them without
verifying the demo notebook still renders correctly, and update the
"Pinned versions" table in `README.md` at the repository root accordingly.

| Package | Version |
|---------|---------|
| jupyterlite-core | 0.8.0 |
| jupyterlite-pyodide-kernel | 0.8.1 |
| jupyter-server | 2.20.0 |

## Directory layout

```
jupyterlite/
├── requirements.txt # Build-time Python deps (pinned)
├── jupyter_lite_config.json # Build configuration
├── jupyter-lite.json # Runtime configuration (kernel, disabled features)
├── README.md # This file
├── .gitignore # Excludes _output/ from version control
└── files/ # Content shipped into the JupyterLite site
├── demo.ipynb # Example notebook
└── data/
└── sample.csv # Generic sample dataset
```

The `_output/` directory is the build artefact and is **not committed** to
version control (see `.gitignore`).
31 changes: 31 additions & 0 deletions jupyterlite/files/data/sample.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
city,country,year,population,gdp_per_capita_usd,life_expectancy
London,UK,2022,9648110,46510,81.2
Berlin,Germany,2022,3677472,50210,81.3
Paris,France,2022,2161000,44020,82.5
Madrid,Spain,2022,3305408,30120,83.4
Rome,Italy,2022,2873494,34060,83.6
Amsterdam,Netherlands,2022,921402,57800,82.1
Brussels,Belgium,2022,1222637,47300,81.5
Vienna,Austria,2022,1897491,53120,81.7
Stockholm,Sweden,2022,975551,55300,82.8
Oslo,Norway,2022,698660,89140,83.2
Copenhagen,Denmark,2022,794128,67440,81.6
Helsinki,Finland,2022,658864,50520,82.0
Dublin,Ireland,2022,592713,99260,82.3
Warsaw,Poland,2022,1794166,18820,78.5
Prague,Czech Republic,2022,1335084,26630,79.1
Budapest,Hungary,2022,1752704,18400,76.7
Bucharest,Romania,2022,2161347,15280,76.0
Sofia,Bulgaria,2022,1307439,12500,75.1
Zagreb,Croatia,2022,806341,17760,78.2
Athens,Greece,2022,664046,20110,82.2
Lisbon,Portugal,2022,545245,24640,82.1
Riga,Latvia,2022,605802,21060,75.8
Vilnius,Lithuania,2022,580020,23480,76.4
Tallinn,Estonia,2022,437619,26840,78.0
Ljubljana,Slovenia,2022,295504,28930,81.4
Bratislava,Slovakia,2022,477174,21300,77.5
Nicosia,Cyprus,2022,269700,29330,81.0
Valletta,Malta,2022,5828,31640,82.7
Luxembourg City,Luxembourg,2022,132780,119720,82.3
Reykjavik,Iceland,2022,131136,73960,83.1
Loading
Loading