From 90576210aeea6a4a02c0e0d8cb4051620340509d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Jul 2026 15:41:10 +0000 Subject: [PATCH 1/3] Add spec for JupyterLite demo page (issue #21) Draft specification for a self-contained JupyterLite demo published in its own subdirectory of the Pages site, rendering in-browser matplotlib figures from sample tabular data. Additive to the frozen playground with independent pins. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01A9Nsrf2pVqE99cBWSnqQQz --- .specify/feature.json | 2 +- .../checklists/requirements.md | 39 ++++ specs/049-jupyterlite-demo/spec.md | 205 ++++++++++++++++++ 3 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 specs/049-jupyterlite-demo/checklists/requirements.md create mode 100644 specs/049-jupyterlite-demo/spec.md diff --git a/.specify/feature.json b/.specify/feature.json index 97b2861..084a46c 100644 --- a/.specify/feature.json +++ b/.specify/feature.json @@ -1,3 +1,3 @@ { - "feature_directory": "specs/048-tab-autocomplete" + "feature_directory": "specs/049-jupyterlite-demo" } diff --git a/specs/049-jupyterlite-demo/checklists/requirements.md b/specs/049-jupyterlite-demo/checklists/requirements.md new file mode 100644 index 0000000..d51aaa6 --- /dev/null +++ b/specs/049-jupyterlite-demo/checklists/requirements.md @@ -0,0 +1,39 @@ +# Specification Quality Checklist: JupyterLite Demo Page + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-07-04 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- Items marked incomplete require spec updates before `/speckit-clarify` or `/speckit-plan`. +- The spec deliberately keeps hosting path (e.g. `/jupyterlite/`) and the + specific build toolchain as implementation details for `/speckit-plan`. +- Constitution VI (pinning) and Principle IV (browser-only) are satisfied by + FR-008 / FR-010 and the Assumptions; the JupyterLite toolchain pins are flagged + as a planning-stage constitution amendment rather than a spec decision. diff --git a/specs/049-jupyterlite-demo/spec.md b/specs/049-jupyterlite-demo/spec.md new file mode 100644 index 0000000..9bf2e42 --- /dev/null +++ b/specs/049-jupyterlite-demo/spec.md @@ -0,0 +1,205 @@ +# Feature Specification: JupyterLite Demo Page + +**Feature Branch**: `claude/speckit-issue-21-hjjn3n` +**Created**: 2026-07-04 +**Status**: Draft +**Input**: GitHub issue #21 — "Add a JupyterLite demo page to the +Pages site (in-browser matplotlib from tabular data)" + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - See in-browser matplotlib plotting on tabular data (Priority: P1) + +A visitor to the public Pages site follows a link to a JupyterLite +demo, opens a bundled example notebook, and runs it. Python executes +entirely in their browser (Pyodide), reads a small sample tabular +dataset, and renders a matplotlib figure inline in the notebook — +with no local install, no accounts, and no server. + +**Why this priority**: This is the whole point of the issue. Without +an in-browser matplotlib figure rendering from sample tabular data, +the demo delivers no value. It is the minimum shippable artefact. + +**Independent Test**: Open the demo URL in a mainstream browser on a +cold load, open the example notebook, run all cells, and confirm a +matplotlib figure appears inline. This is fully testable on its own +and delivers the complete headline value. + +**Acceptance Scenarios**: + +1. **Given** the deployed Pages site, **When** a visitor opens the + JupyterLite demo URL on a cold load in Chrome or Firefox, **Then** + JupyterLite loads and the example notebook is available to open. +2. **Given** the example notebook is open, **When** the visitor runs + all cells, **Then** the sample tabular dataset loads and at least + one matplotlib figure renders inline within the notebook. +3. **Given** the notebook has produced a figure, **When** the visitor + inspects the running environment, **Then** no server or account was + required — all execution happened in the browser. + +--- + +### User Story 2 - Discover the demo from the landing page (Priority: P2) + +A visitor on the existing site's landing page sees a clearly labelled +link to the JupyterLite demo and can navigate to it, understanding +that it is a separate, notebook/plotting surface complementing the +existing Frictionless playground. + +**Why this priority**: A demo that ships but is unreachable from the +site delivers little value; discoverability is what turns the build +into something visitors actually see. It depends on Story 1 existing +but is a distinct, independently testable slice. + +**Independent Test**: Load the landing page, confirm a visible link to +the demo is present, click it, and confirm it navigates to the demo +page. + +**Acceptance Scenarios**: + +1. **Given** the landing page, **When** a visitor reads it, **Then** a + labelled link to the JupyterLite demo is visible. +2. **Given** the link, **When** the visitor clicks it, **Then** they + arrive at the JupyterLite demo page. + +--- + +### User Story 3 - Reproduce the demo build from one documented command (Priority: P3) + +A maintainer can rebuild the JupyterLite demo locally and produce the +same published output by following a single documented command, so the +demo is reproducible and the frozen playground build stays untouched. + +**Why this priority**: Reproducibility and additive-build hygiene +protect the pinned/frozen playground (Constitution VI) and keep the +demo maintainable, but the visitor-facing value (Stories 1–2) can be +demonstrated before this is polished. + +**Independent Test**: On a clean checkout, run the single documented +build command and confirm it produces the demo output directory +without modifying or rebuilding the existing playground. + +**Acceptance Scenarios**: + +1. **Given** a clean checkout, **When** a maintainer runs the single + documented build command, **Then** the demo output is produced in + its own dedicated subdirectory. +2. **Given** the demo build, **When** it runs, **Then** it neither + modifies nor depends on the existing frozen playground's runtime or + pins. +3. **Given** the site deploy, **When** the Pages workflow runs, **Then** + the demo build is an additive step that publishes the demo alongside + the existing site. + +--- + +### Edge Cases + +- **Cold load latency**: The demo fetches Pyodide and package wheels + (including matplotlib and its dependencies) from a CDN at runtime. + On a first (uncached) visit this download can be large and slow; the + demo should set expectations for load time rather than appear broken. +- **Network unavailable / CDN blocked**: Because the demo is online-only + by design, it cannot run in an offline or air-gapped context. When the + CDN is unreachable, the notebook cannot execute; this limitation must + be documented so expectations are set. +- **Unsupported / older browser**: A browser without the capabilities + Pyodide requires will not run the demo. Mainstream current Chrome and + Firefox are the supported baseline. +- **Payload weight from data libraries**: If the sample notebook pulls + in heavier libraries (e.g. numpy/pandas), the wheel download grows. + Keep the sample light or note the expected load cost. +- **Sample data scope**: Only generic, shareable sample data is used; + no project-specific or sensitive datasets appear in the public repo. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: The site MUST publish a self-contained JupyterLite demo + reachable at a dedicated path on the existing Pages site (its own + subdirectory), separate from the existing playground. +- **FR-002**: The demo MUST render matplotlib output entirely in the + browser (via Pyodide), with figures appearing inline in notebook + cells — no server and no account required. +- **FR-003**: The demo MUST ship at least one example notebook that + loads a generic sample tabular dataset (e.g. CSV or Frictionless + datapackage) and produces at least one matplotlib figure. +- **FR-004**: The landing page MUST include a visible, labelled link to + the JupyterLite demo. +- **FR-005**: The demo MUST work from a cold (uncached) load in current + mainstream browsers (Chrome and Firefox). +- **FR-006**: The demo build MUST be reproducible via a single + documented command. +- **FR-007**: The demo build MUST be an additive step in the existing + Pages deploy workflow that publishes the demo alongside the existing + site. +- **FR-008**: The demo MUST live in its own subdirectory with its own + pinned versions, and MUST NOT modify, rebuild, or share the runtime + or pins of the existing frozen playground (Constitution VI). +- **FR-009**: The demo MUST use only generic, shareable sample data; no + project-specific or sensitive datasets in the public repo. +- **FR-010**: The demo's online-only nature (runtime CDN fetch of + Pyodide and wheels, no offline/air-gapped path) and expected cold-load + cost MUST be documented so visitor and maintainer expectations are set + (Constitution Principle VI — Limitations). + +### Key Entities *(include if feature involves data)* + +- **JupyterLite demo site**: The self-contained, statically-served + notebook environment published in its own subdirectory of the Pages + site, with its own pinned versions independent of the playground. +- **Example notebook**: A shipped notebook that loads a sample tabular + dataset and renders one or more matplotlib figures inline. +- **Sample tabular dataset**: A generic, shareable dataset (CSV or + Frictionless datapackage) used as the plotting input; kept light to + bound download weight. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: From the landing page, a visitor can reach the JupyterLite + demo in a single click via a clearly labelled link. +- **SC-002**: On a cold load in current Chrome and Firefox, a visitor + can open the example notebook, run all cells, and see at least one + matplotlib figure rendered inline. +- **SC-003**: 100% of the demo's execution (data load and plotting) + happens in the browser — zero server calls beyond fetching pinned + static assets and CDN-hosted Pyodide/wheels; no account is required. +- **SC-004**: A maintainer can reproduce the published demo output from + a single documented command on a clean checkout, with the existing + frozen playground's build output unchanged. +- **SC-005**: The demo's online-only limitation and expected cold-load + behaviour are stated in the project's limitations documentation. + +## Assumptions + +- **Numbering**: GitHub issue #21 is a newly filed feature and is + unrelated to the pre-existing `specs/021-file-tree` directory; the two + numbering systems are independent. This spec is filed under the next + sequential spec directory, `049-jupyterlite-demo`. +- **Hosting**: The demo is published on the same public GitHub Pages + site as the existing playground (`https://deepbluecltd.github.io/ + tabular-data-playground/`), under its own subdirectory (e.g. + `/jupyterlite/`). Exact path is an implementation detail. +- **Online-only is acceptable here**: Runtime CDN fetching is fine for a + public Pages demo (Pages is online anyway) and does not violate the + browser-only/no-backend posture (Constitution Principle IV), which + permits fetching pinned static assets. +- **Additive to the freeze**: The existing playground is deliberately + pinned (Constitution VI). The demo has its own pins and runtime and + must not alter the existing contract. +- **New tooling is a constitution matter**: Introducing the JupyterLite + build toolchain and its pins is expected to be recorded as a + constitution Technology-Constraints amendment (with pins in the + README), handled during planning — not a silent dependency addition. +- **Sample data**: A generic, light sample tabular dataset is used; + keeping numpy/pandas usage minimal bounds the wheel download. + +## Out of Scope + +- Offline / vendored-wheel bundling for an air-gapped path (separate + concern). +- Any change to the existing frozen playground's runtime, pins, or + build output. From 335df57b50117a81ed3a8318d24e6a9ae79ce7ff Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Jul 2026 15:49:55 +0000 Subject: [PATCH 2/3] Add implementation plan for JupyterLite demo page (issue #21) Phase 0 research + Phase 1 design artefacts: plan.md, research.md, data-model.md, contracts/ (site-surface, build-command), quickstart.md. Flags the required constitution Technology-Constraints amendment (JupyterLite is a new infrastructural dependency) and scopes README pins + docs/limitations.md into the implementation. Additive build via jupyter lite build into _site/jupyterlite/, frozen playground untouched. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01A9Nsrf2pVqE99cBWSnqQQz --- CLAUDE.md | 1 + .../contracts/build-command.md | 54 +++++++ .../contracts/site-surface.md | 40 +++++ specs/049-jupyterlite-demo/data-model.md | 66 +++++++++ specs/049-jupyterlite-demo/plan.md | 139 ++++++++++++++++++ specs/049-jupyterlite-demo/quickstart.md | 58 ++++++++ specs/049-jupyterlite-demo/research.md | 133 +++++++++++++++++ 7 files changed, 491 insertions(+) create mode 100644 specs/049-jupyterlite-demo/contracts/build-command.md create mode 100644 specs/049-jupyterlite-demo/contracts/site-surface.md create mode 100644 specs/049-jupyterlite-demo/data-model.md create mode 100644 specs/049-jupyterlite-demo/plan.md create mode 100644 specs/049-jupyterlite-demo/quickstart.md create mode 100644 specs/049-jupyterlite-demo/research.md diff --git a/CLAUDE.md b/CLAUDE.md index 998a9de..897195e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,6 +3,7 @@ For additional context about technologies to be used, project structure, shell commands, and other important information, read the spec at `spec.md` and the constitution at `.specify/memory/constitution.md`. Active per-item plans live under `specs//plan.md`. +Current active plan: `specs/049-jupyterlite-demo/plan.md`. # Spec-Driven Development on a Project Board diff --git a/specs/049-jupyterlite-demo/contracts/build-command.md b/specs/049-jupyterlite-demo/contracts/build-command.md new file mode 100644 index 0000000..e795e59 --- /dev/null +++ b/specs/049-jupyterlite-demo/contracts/build-command.md @@ -0,0 +1,54 @@ +# Contract: Reproducible Build Command + +Satisfies FR-006 ("reproducible via a single documented command") and FR-007 +(additive workflow step). + +## Single documented command (local + CI) + +From the repository root, with a pinned Python environment: + +```bash +pip install -r jupyterlite/requirements.txt \ + && jupyter lite build \ + --config jupyterlite/jupyter_lite_config.json \ + --output-dir jupyterlite/_output +``` + +`jupyter_lite_config.json` fixes the content root (`jupyterlite/content/`) and +output location; the base URL is supplied per environment (see below). This +one command is documented verbatim in `jupyterlite/README.md`. + +## Base URL parameterisation + +JupyterLite bakes its base URL at build time (analogous to the IDE's +`VITE_BASE_PATH`). The publish path differs by environment: + +| Environment | Base URL | +|-------------|----------| +| Deploy (`main`) | `/tabular-data-playground/jupyterlite/` | +| PR preview | `/tabular-data-playground/pr-preview/pr-/jupyterlite/` | + +The base URL is passed via the JupyterLite config / CLI flag at build time in +each workflow; local builds may use the default/root base for quick checks. + +## Workflow integration (additive) + +In **`.github/workflows/deploy.yml`** and **`.github/workflows/pr-preview.yml`**, +after the existing IDE build + copy and before the peaceiris publish: + +1. `actions/setup-python@` (Python 3.11+). +2. `pip install -r jupyterlite/requirements.txt`. +3. `jupyter lite build` with the environment's base URL. +4. `cp -r jupyterlite/_output/. _site/jupyterlite/` (after + `mkdir -p _site/jupyterlite`). + +## Invariants + +- **C1**: The command exits 0 and produces a non-empty output dir on a clean + checkout (CI guard). +- **C2**: The step does **not** invoke pnpm, does not touch `app/`, and does + not alter the existing IDE/`slides`/`sample-package` assembly. It only adds + `_site/jupyterlite/`. +- **C3**: All Action versions and Python deps are pinned (Constitution VI). +- **C4**: Re-running on the same commit is idempotent (same inputs → same + published tree, modulo timestamps). diff --git a/specs/049-jupyterlite-demo/contracts/site-surface.md b/specs/049-jupyterlite-demo/contracts/site-surface.md new file mode 100644 index 0000000..691498e --- /dev/null +++ b/specs/049-jupyterlite-demo/contracts/site-surface.md @@ -0,0 +1,40 @@ +# Contract: Published Site URL Surface + +The demo adds one new path to the published `gh-pages` tree. The existing +surface is unchanged. + +## Deploy (push to `main`) — served under `/tabular-data-playground/` + +| Path | Serves | Status | +|------|--------|--------| +| `/` | Static welcome page (`web/index.html`) | EXISTING — **edited**: gains a labelled link to `/jupyterlite/` | +| `/slides/` | reveal.js findings deck | EXISTING — unchanged | +| `/playground/` | Frozen Vite IDE (`app/dist/`) | EXISTING — **unchanged** (frozen; FR-008) | +| `/sample-package/` | Lesson 8 Data Package | EXISTING — unchanged | +| `/jupyterlite/` | **NEW** — JupyterLite demo site (`jupyter lite build` output) | NEW | + +## PR preview — served under `/pr-preview/pr-/` + +The same relative surface is reproduced per PR, so the demo is reviewable +before merge: + +- `/pr-preview/pr-/` → welcome (with the link) +- `/pr-preview/pr-/slides/` +- `/pr-preview/pr-/playground/` +- `/pr-preview/pr-/jupyterlite/` → **NEW** demo + +## Guarantees + +- **G1 (discoverability, FR-004)**: The welcome page at `/` (and per-PR + equivalent) contains a visible, labelled hyperlink whose target resolves to + the `/jupyterlite/` demo under the same base. +- **G2 (headline, FR-002/FR-003)**: Opening `/jupyterlite/`, opening + `demo.ipynb`, and running all cells renders ≥1 matplotlib figure inline, in + the browser, with no server call beyond static-asset + CDN wheel fetches. +- **G3 (cold load, FR-005)**: G2 holds on a first uncached visit in current + Chrome and Firefox. +- **G4 (isolation, FR-008)**: The `/playground/` bytes and the frozen pins are + identical before and after this change; `keep_files: true` and PR-preview + behaviour are preserved. +- **G5 (no leak, FR-009)**: Nothing under `/jupyterlite/` contains + project-specific or sensitive data. diff --git a/specs/049-jupyterlite-demo/data-model.md b/specs/049-jupyterlite-demo/data-model.md new file mode 100644 index 0000000..66ac751 --- /dev/null +++ b/specs/049-jupyterlite-demo/data-model.md @@ -0,0 +1,66 @@ +# Phase 1 Data Model: JupyterLite Demo Page + +This feature is a static demo, not a data-processing system. There is no +persistent server-side data and no runtime schema. The "entities" below are +the shipped build artefacts and their relationships. + +## Entities + +### JupyterLite demo site + +- **What it is**: The static site emitted by `jupyter lite build`, published at + `/tabular-data-playground/jupyterlite/` (deploy) or the PR-scoped preview + path. +- **Composed of**: The JupyterLite app shell + the Pyodide kernel assets + + the ingested `content/` (notebooks + data). +- **Source of truth**: `jupyterlite/` directory (config + requirements + + content). Output is derived and not committed. +- **Validation / invariants**: + - Built output directory (`_output/`) is non-empty after a successful build. + - Base URL baked at build time matches the publish path (deploy vs preview). + - Contains no project-specific or sensitive data (FR-009). + - Independent of the frozen playground's assets and pins (FR-008). + +### Example notebook (`demo.ipynb`) + +- **What it is**: The shipped notebook a visitor opens and runs. +- **Fields / structure**: An ordered list of cells that (1) optionally install + matplotlib in-browser, (2) load `data/sample.csv`, (3) produce ≥1 matplotlib + figure rendered inline. +- **Relationships**: Reads the Sample tabular dataset; runs on the Pyodide + kernel; lives inside the JupyterLite demo site's `content/`. +- **Validation / invariants**: + - Run-All produces at least one inline matplotlib figure (FR-002, FR-003). + - Uses only the pinned kernel + explicitly installed packages. + +### Sample tabular dataset (`data/sample.csv`) + +- **What it is**: A generic, light CSV used as plotting input. +- **Fields / structure**: A small number of columns × a few dozen rows; + neutral/public content. +- **Relationships**: Consumed by `demo.ipynb`. +- **Validation / invariants**: + - Generic and shareable; no sensitive or project-specific data (FR-009). + - Small enough to keep payload light (research R6). + +### Pin set (`jupyterlite/requirements.txt` + README rows) + +- **What it is**: The exact-version pins for the demo toolchain. +- **Fields**: `jupyterlite-core==X.Y.Z`, `jupyterlite-pyodide-kernel==A.B.C`, + plus recorded Pyodide (carried by the kernel) and matplotlib versions in the + README table. +- **Relationships**: Independent of the frozen playground's Pyodide `0.27.7`. +- **Validation / invariants**: + - All pins are exact (`==`), no ranges (Constitution VI). + - Every pin is recorded in `README.md` (gate 5). + +## State transitions + +None at runtime. The only lifecycle is the build/publish pipeline: + +`jupyterlite/ source` → `jupyter lite build` → `_output/` → copied to +`_site/jupyterlite/` → published to `gh-pages` → served at `/jupyterlite/`. + +Visitor-side, the notebook has ordinary Jupyter cell execution state, held +entirely in the browser (JupyterLite IndexedDB); it never reaches a server and +is out of scope for persistence. diff --git a/specs/049-jupyterlite-demo/plan.md b/specs/049-jupyterlite-demo/plan.md new file mode 100644 index 0000000..3dbba3b --- /dev/null +++ b/specs/049-jupyterlite-demo/plan.md @@ -0,0 +1,139 @@ +# Implementation Plan: JupyterLite Demo Page + +**Branch**: `claude/speckit-issue-21-hjjn3n` | **Date**: 2026-07-04 | **Spec**: [spec.md](./spec.md) +**Input**: Feature specification from `specs/049-jupyterlite-demo/spec.md` + +## Summary + +Publish a self-contained JupyterLite site to `/jupyterlite/` on the existing +GitHub Pages tree. It ships one example notebook that loads a light, generic +sample CSV and renders a matplotlib figure inline — all in the browser, no +server, no accounts. The build (`jupyter lite build`) runs as an **additive** +step in the existing deploy and PR-preview workflows and copies its output +into the assembled `_site/jupyterlite/`, leaving the frozen playground's +build (`/playground/`) and pins entirely untouched. The demo carries its own +Python pins in a dedicated `jupyterlite/` source directory. Because +JupyterLite is a **new infrastructural dependency**, this feature carries a +required **constitution amendment** (Technology Constraints) and updates to +`README.md` pins and `docs/limitations.md` — all landed in the same change as +the implementation. + +## Technical Context + +**Language/Version**: Python 3.11+ (build-time only, for `jupyter lite build`); notebooks run against the JupyterLite Pyodide kernel's bundled Python in-browser. No change to the app's TypeScript/React stack. +**Primary Dependencies**: `jupyterlite-core` (pinned), `jupyterlite-pyodide-kernel` (pinned); matplotlib provided in-browser by the Pyodide kernel / `piplite`. Build orchestrated by `jupyter lite build`. +**Storage**: None. Static files only. Notebooks and sample data are shipped read-only; visitor edits live in the browser (JupyterLite's own IndexedDB), never persisted server-side. +**Testing**: Manual cold-load verification in Chrome + Firefox (run-all-cells → figure renders). Optional Playwright Chromium smoke against the built `/jupyterlite/` page (open notebook, run, assert an ``/canvas figure appears). CI must confirm the build produces a non-empty `_output/`. +**Target Platform**: Static GitHub Pages (`gh-pages` branch); WASM/Pyodide in current Chrome & Firefox. +**Project Type**: Additive static sub-site alongside an existing single-page app; independent build pipeline. +**Performance Goals**: Not a hot path. Set expectations for cold-load: first uncached visit fetches Pyodide + matplotlib wheels from CDN (tens of MB); subsequent loads are browser-cached. No hard latency budget beyond "loads and runs on a cold visit." +**Constraints**: Online-only by design (runtime CDN fetch; no offline/air-gapped path). Must not modify, rebuild, or share the frozen `/playground/` runtime or pins. Generic/shareable sample data only. +**Scale/Scope**: One JupyterLite site, one (optionally two) example notebook(s), one small sample CSV. Single visitor per browser session; no shared state (Constitution Principle IV). + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-checked after Phase 1 design.* + +Per-feature gates (constitution "Development Workflow & Quality Gates"): + +1. **Research-first** — PASS. The demo evaluates a complementary slice of the + in-browser Python/plotting story (Pyodide + matplotlib via JupyterLite) and + is a legitimate research surface, not product scope creep. It is explicitly + additive and disposable relative to the frozen playground. +2. **Notes-section** — N/A. This is not a curriculum lesson template, so the + Notes & Observations requirement does not apply. (Observations about + JupyterLite/matplotlib-under-Pyodide still land in `docs/limitations.md`.) +3. **Destruction** — PASS. No flow overwrites or deletes user content. Visitor + edits are sandboxed in JupyterLite's own browser storage; nothing touches + the playground's `/workspace` or IndexedDB. No modal confirmation needed. +4. **Backend** — PASS. No backend, account, or telemetry. All execution is + in-browser; the only network use is fetching pinned static assets and + CDN-hosted Pyodide/wheels, which Principle IV expressly permits. +5. **Pinning** — CONDITIONAL. The feature introduces new external + dependencies (`jupyterlite-core`, `jupyterlite-pyodide-kernel`, the + in-browser matplotlib/Pyodide the kernel pulls). They MUST land pinned and + recorded in `README.md`. **Additionally**, the constitution's Technology + Constraints class this as *adding a new infrastructural dependency*, which + "IS an amendment." → tracked in Complexity Tracking; a constitution + amendment (Technology Constraints + Sync Impact Report + version bump) is + part of the implementation change. +6. **Limitations** — CONDITIONAL. The demo introduces new sharp edges + (online-only, no air-gapped path; cold-load wheel weight; separate pin set + from the frozen playground). `docs/limitations.md` MUST be updated in the + same change. Satisfied by FR-010 and a planned limitations entry. + +**Gate verdict**: No hard violation. Two conditional gates (Pinning as an +amendment, Limitations) are honoured by scoping the constitution amendment, +README pins, and limitations entry into the implementation. See Complexity +Tracking. + +Post-Phase-1 re-check: design keeps the demo in its own `jupyterlite/` source +tree with its own pins and an additive workflow step; the frozen playground's +build and pins are unchanged. Gates still hold. + +## Project Structure + +### Documentation (this feature) + +```text +specs/049-jupyterlite-demo/ +├── plan.md # This file (/speckit-plan output) +├── research.md # Phase 0 output +├── data-model.md # Phase 1 output +├── quickstart.md # Phase 1 output +├── contracts/ # Phase 1 output (URL surface + build-command contract) +│ ├── site-surface.md +│ └── build-command.md +├── checklists/ +│ └── requirements.md # From /speckit-specify +└── tasks.md # /speckit-tasks output (NOT created here) +``` + +### Source Code (repository root) + +```text +jupyterlite/ # NEW — self-contained demo source, own pins +├── requirements.txt # Pinned: jupyterlite-core, jupyterlite-pyodide-kernel +├── jupyter_lite_config.json # Build config (output dir, base URL /jupyterlite/) +├── jupyter-lite.json # Runtime config (kernel, disabled features) +├── README.md # One documented build command (FR-006) +└── content/ # Shipped into the JupyterLite site + ├── demo.ipynb # Example notebook: load CSV → matplotlib figure + └── data/ + └── sample.csv # Generic, light sample tabular dataset + +web/ +└── index.html # EDIT — add visible link to /jupyterlite/ (FR-004) + +.github/workflows/ +├── deploy.yml # EDIT — additive: build JupyterLite, copy to _site/jupyterlite/ +└── pr-preview.yml # EDIT — same additive step for PR previews + +README.md # EDIT — record JupyterLite/kernel/matplotlib pins +docs/limitations.md # EDIT — online-only + cold-load + separate-pins entry +.specify/memory/constitution.md # EDIT — Technology Constraints amendment (+ Sync Impact, version bump) +``` + +**Structure Decision**: A new top-level `jupyterlite/` directory holds the +demo's source with its **own** `requirements.txt` pins, fully isolated from +`app/` (the frozen playground). The two published surfaces — `/playground/` +(Vite/pnpm) and `/jupyterlite/` (`jupyter lite build`) — are built by +independent toolchains and only meet in the workflows' "assemble `_site`" +step, where the JupyterLite `_output/` is copied to `_site/jupyterlite/` +after the existing IDE copy. This mirrors how `/slides/` and `/playground/` +already coexist and preserves `keep_files: true` / PR-preview behaviour. + +## Complexity Tracking + +> Filled because Constitution Check surfaced an amendment-class dependency. + +| Violation | Why Needed | Simpler Alternative Rejected Because | +|-----------|------------|--------------------------------------| +| New infrastructural dependency (`jupyterlite-core` + `jupyterlite-pyodide-kernel` + a Python build step in CI) — per Technology Constraints this "IS an amendment" | The issue's headline value is an in-browser JupyterLite notebook rendering matplotlib; JupyterLite is the named vehicle and cannot be delivered with the existing Vite/pnpm stack alone | Reusing the frozen playground's Pyodide worker to fake a notebook UI would (a) modify the frozen runtime (forbidden by the issue and Principle VI) and (b) not be a real JupyterLite/notebook surface the issue asks for | +| Second pin set (demo pins independent of the frozen playground's Pyodide `0.27.7`) | The demo must not share or shift the frozen contract; its Pyodide comes from the pinned kernel, decoupled on purpose | A single shared pin set would couple the demo to the freeze and risk the "must not modify the frozen playground" constraint | + +**Amendment obligation**: implementation MUST include a constitution +amendment adding a "JupyterLite demo" entry under Technology Constraints +(with Sync Impact Report + MINOR version bump), the README pin rows, and the +`docs/limitations.md` entry — all in the same PR as the code, per the +constitution's own rule and gates 5–6. diff --git a/specs/049-jupyterlite-demo/quickstart.md b/specs/049-jupyterlite-demo/quickstart.md new file mode 100644 index 0000000..cc2a3c3 --- /dev/null +++ b/specs/049-jupyterlite-demo/quickstart.md @@ -0,0 +1,58 @@ +# Quickstart: JupyterLite Demo Page + +How to build, run, and verify the demo locally, and what lands in the repo. + +## Build locally + +```bash +# From repo root, in a Python 3.11+ environment +pip install -r jupyterlite/requirements.txt +jupyter lite build \ + --config jupyterlite/jupyter_lite_config.json \ + --output-dir jupyterlite/_output + +# Serve the built site (any static server); e.g. +python -m http.server -d jupyterlite/_output 8000 +# open http://localhost:8000/ +``` + +## Verify (matches acceptance criteria) + +1. **Discoverable (FR-004, SC-001)**: On the built welcome page (`web/index.html` + in the assembled site), a labelled link points to the JupyterLite demo. +2. **Headline (FR-002/FR-003, SC-002)**: Open the demo, open `demo.ipynb`, + Run All → a matplotlib figure renders inline. +3. **Cold load (FR-005)**: Repeat step 2 in a fresh Chrome and Firefox profile + (empty cache) → figure still renders (allow time for the CDN wheel fetch). +4. **In-browser only (SC-003)**: Network tab shows only static-asset + CDN + (Pyodide/wheel) requests; no app backend, no account prompt. +5. **Isolation (FR-008, G4)**: `/playground/` assets and README pins for the + frozen playground are unchanged by this feature. + +## Files this feature adds/edits + +**New** (`jupyterlite/`): +- `requirements.txt` — pinned `jupyterlite-core`, `jupyterlite-pyodide-kernel` +- `jupyter_lite_config.json`, `jupyter-lite.json` — build/runtime config +- `README.md` — the single documented build command +- `content/demo.ipynb` — example notebook +- `content/data/sample.csv` — generic sample dataset + +**Edited**: +- `web/index.html` — link to `/jupyterlite/` +- `.github/workflows/deploy.yml`, `.github/workflows/pr-preview.yml` — additive + build+copy step +- `README.md` — JupyterLite/kernel/Pyodide/matplotlib pin rows +- `docs/limitations.md` — online-only + cold-load + separate-pins entry +- `.specify/memory/constitution.md` — Technology Constraints amendment + (+ Sync Impact Report + MINOR version bump) + +## Gotchas + +- **Base URL is build-time.** A build made for one path won't work served at a + different path. Use the environment's base URL (see + `contracts/build-command.md`). +- **First load is heavy.** Pyodide + matplotlib wheels download on the cold + visit; this is expected and documented in `docs/limitations.md`. +- **Online-only.** No offline/air-gapped path; wheel bundling is out of scope. +- **`_output/` is derived** — do not commit it (add to `.gitignore`). diff --git a/specs/049-jupyterlite-demo/research.md b/specs/049-jupyterlite-demo/research.md new file mode 100644 index 0000000..740f393 --- /dev/null +++ b/specs/049-jupyterlite-demo/research.md @@ -0,0 +1,133 @@ +# Phase 0 Research: JupyterLite Demo Page + +All Technical-Context unknowns are resolved below. No `NEEDS CLARIFICATION` +markers remain. + +## R1 — Build tool: `jupyter lite build` (jupyterlite-core) + +- **Decision**: Build the demo with the JupyterLite CLI: `jupyter lite build` + driven by a `jupyterlite-core` pinned in `jupyterlite/requirements.txt`. The + CLI reads `jupyter_lite_config.json`, ingests `jupyterlite/content/`, and + emits a static site to `_output/`. +- **Rationale**: This is the canonical, single-command JupyterLite build (the + issue's "reproducible via a single documented command" acceptance). It + produces a fully static tree suitable for `gh-pages`, matching how the site + already publishes static artefacts via peaceiris. +- **Alternatives considered**: Hand-rolling a Pyodide notebook UI inside the + existing app — rejected: it would touch the frozen playground runtime and + isn't a real JupyterLite surface. Using a hosted JupyterLite template repo — + rejected: adds an opaque external template; in-repo source keeps pins and + content reviewable and reproducible. + +## R2 — Kernel: `jupyterlite-pyodide-kernel` + +- **Decision**: Ship the Pyodide kernel (`jupyterlite-pyodide-kernel`, pinned) + so notebooks run Python in-browser. matplotlib is obtained in-browser via + the kernel's `piplite`/Pyodide package index at notebook runtime (a + `%pip install matplotlib` or `piplite.install` cell), or is already present + in the kernel's Pyodide distribution. +- **Rationale**: The Pyodide kernel is the standard way to get client-side + Python + scientific stack in JupyterLite, and it is what makes matplotlib + render inline with zero server. Keeping matplotlib install explicit in the + notebook makes the payload weight visible and controllable (issue's + "payload weight" consideration). +- **Alternatives considered**: Bundling/vendoring wheels into the site for an + offline path — explicitly **out of scope** (issue) and heavier; the demo is + online-only by design. The pure-JS/xeus kernels — rejected: no Python/ + matplotlib. + +## R3 — Pinning strategy (Constitution VI + gate 5) + +- **Decision**: Pin exact versions in `jupyterlite/requirements.txt` with + `==` (no ranges), e.g.: + ``` + jupyterlite-core== + jupyterlite-pyodide-kernel== + ``` + The concrete version numbers are locked at implementation time from the + build environment (`pip index versions` / the installed set) and recorded + in `README.md`'s "Pinned versions" table alongside the Pyodide version the + kernel carries and the matplotlib version the notebook installs. The demo's + pins are **independent** of the frozen playground's Pyodide `0.27.7`. +- **Rationale**: The constitution makes pinning non-negotiable and requires + pins recorded in the README; exact `==` pins make any tagged build + reproducible. Independence from the frozen playground honours "must not + modify or share the frozen runtime." +- **Alternatives considered**: Sharing the playground's Pyodide pin — rejected: + couples the demo to the freeze. Unpinned "latest" — rejected: violates + Principle VI and makes builds non-reproducible. +- **Note**: Exact version strings are an implementation detail, not a scope + decision, so they are not `NEEDS CLARIFICATION`; the *mechanism* (pinned + `requirements.txt` + README rows) is fixed here. + +## R4 — Publish path & workflow integration (additive) + +- **Decision**: Publish to `/jupyterlite/`. In both `deploy.yml` and + `pr-preview.yml`, add — **after** the existing IDE copy — a step that: + (a) sets up Python, (b) `pip install -r jupyterlite/requirements.txt`, + (c) runs `jupyter lite build` (output configured for base URL + `/tabular-data-playground/jupyterlite/` on deploy and the PR-scoped base on + previews), (d) copies `_output/.` into `_site/jupyterlite/`. The existing + pnpm IDE build, `keep_files: true`, and sample-package handling are + untouched. +- **Rationale**: Mirrors the established pattern where `/slides/` and + `/playground/` are assembled into `_site` from independent sources; keeps + the change additive and the frozen playground build byte-for-byte unchanged + (FR-007, FR-008). PR previews get the demo too, so it's reviewable before + merge (the preview bot already lists welcome/slides/IDE URLs; the demo joins + that tree). +- **Alternatives considered**: A separate standalone workflow — rejected: + duplicates checkout/publish plumbing and risks racing peaceiris publishes to + the same `gh-pages` branch. Publishing the demo at the site root — rejected: + the issue asks for a dedicated subdirectory and root is the welcome page. +- **Base-path caveat**: JupyterLite bakes its base URL at build time. Deploy + and PR-preview builds therefore need different base URLs (like the IDE's + `VITE_BASE_PATH`). This is captured in `contracts/build-command.md`. + +## R5 — Landing-page link target (FR-004) + +- **Decision**: Add the visible, labelled link to the **static site welcome + page** `web/index.html` (served at `/`), pointing at `/jupyterlite/`. +- **Rationale**: "Linked from the landing page" most naturally means the + public site entry point that every visitor hits first; `web/index.html` is + static, reproducible, and outside the frozen app, so the link needs no app + rebuild. It sits beside the existing welcome→slides/playground links. +- **Alternatives considered**: Linking from the in-app React landing page + (spec 041, inside `/playground/`) — rejected: that lives inside the frozen + playground and would mean editing the frozen app; the issue's intent (site + discoverability) is served better and more cheaply from the site welcome + page. If the maintainer wants both, adding the in-app link later is a + separate, optional change. + +## R6 — Sample data (FR-003, FR-009, payload weight) + +- **Decision**: Ship one small, generic CSV (e.g. a handful of columns × a few + dozen rows — a neutral dataset such as monthly values or a classic public + sample) under `jupyterlite/content/data/sample.csv`. The notebook reads it + with the standard library `csv` (or a light pandas read if pandas is already + pulled) and plots with matplotlib. +- **Rationale**: Keeps the wheel download light (the issue flags numpy/pandas + weight) and the data unambiguously shareable/non-sensitive. A pure-`csv` + read avoids forcing pandas into the payload unless a plot genuinely needs it. +- **Alternatives considered**: A Frictionless datapackage sample — acceptable + per the spec but heavier to justify for a plotting demo; CSV is the lightest + path to the headline (a rendered figure). Fetching remote data at runtime — + rejected: adds a failure mode and offline surprise beyond the intended + Pyodide/CDN fetch. + +## R7 — Verification approach (FR-005, testing) + +- **Decision**: Primary verification is a manual cold-load check in Chrome and + Firefox: open `/jupyterlite/`, open `demo.ipynb`, Run All, confirm a + matplotlib figure renders inline. CI adds a cheap guard that + `jupyter lite build` succeeds and `_output/` is non-empty. An optional + Playwright Chromium smoke can open the built page and assert a figure + element appears. +- **Rationale**: The acceptance criteria are visitor-facing and browser-bound; + a build-succeeds guard catches the common regression (broken build) cheaply, + while the manual cold-load check covers the real "works from cold load" + criterion. Full headless notebook execution in CI is higher-cost and can be + deferred. +- **Alternatives considered**: Full automated notebook execution in CI (e.g. + headless Run-All) — deferred: valuable but not required for the MVP and adds + CI weight; can be added later if the demo grows. From 7417c2f7e9829b25c151b842400a302a8f715638 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Jul 2026 16:24:02 +0000 Subject: [PATCH 3/3] Add tasks.md for JupyterLite demo page (issue #21) 23 tasks across 6 phases, organized by the 3 user stories. Setup scaffolds the isolated jupyterlite/ source with pinned toolchain; Foundational lands the required constitution amendment, README pins, and limitations entry; US1 (MVP) renders a matplotlib figure; US2 adds the welcome-page link; US3 wires the additive build into deploy and PR-preview workflows. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01A9Nsrf2pVqE99cBWSnqQQz --- specs/049-jupyterlite-demo/tasks.md | 190 ++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 specs/049-jupyterlite-demo/tasks.md diff --git a/specs/049-jupyterlite-demo/tasks.md b/specs/049-jupyterlite-demo/tasks.md new file mode 100644 index 0000000..9b3be91 --- /dev/null +++ b/specs/049-jupyterlite-demo/tasks.md @@ -0,0 +1,190 @@ +--- + +description: "Task list for JupyterLite Demo Page (issue #21)" +--- + +# Tasks: JupyterLite Demo Page + +**Input**: Design documents from `specs/049-jupyterlite-demo/` +**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/, quickstart.md + +**Tests**: Not requested as TDD. Verification is folded into each story as +manual cold-load checks plus a cheap CI build guard; a Playwright smoke is an +optional polish task. + +**Organization**: Tasks are grouped by user story (from spec.md) so each story +is independently implementable and testable. + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- **[Story]**: Which user story the task belongs to (US1/US2/US3) +- Exact file paths are included in each description + +## Path Conventions + +New demo source lives in a top-level `jupyterlite/` directory (own pins, +isolated from the frozen `app/`). Published surface is `/jupyterlite/` on the +`gh-pages` tree. See plan.md → Project Structure. + +--- + +## Phase 1: Setup (Shared Infrastructure) + +**Purpose**: Scaffold the isolated `jupyterlite/` demo source and its pinned toolchain. + +- [ ] T001 [P] Create the `jupyterlite/` scaffold with `jupyterlite/content/` and `jupyterlite/content/data/` subdirectories +- [ ] T002 [P] Add `jupyterlite/requirements.txt` with exact `==` pins for `jupyterlite-core` and `jupyterlite-pyodide-kernel` (lock the concrete versions from the build env; no ranges — Constitution VI) +- [ ] T003 [P] Add `jupyterlite/jupyter_lite_config.json` setting the content root (`jupyterlite/content/`) and output dir (`jupyterlite/_output`) per contracts/build-command.md +- [ ] T004 [P] Add `jupyterlite/jupyter-lite.json` runtime config selecting the Pyodide kernel and disabling unneeded features +- [ ] T005 [P] Add an ignore rule for the derived build output (`jupyterlite/_output/`) in `jupyterlite/.gitignore` (or root `.gitignore`) so it is never committed + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Land the amendment-class gate obligations and confirm the pinned toolchain builds. These MUST be complete before the demo can ship or a user story can be validated end-to-end. + +**⚠️ CRITICAL**: JupyterLite is a *new infrastructural dependency* — per the constitution's Technology Constraints this requires an amendment landed in the same change (see plan.md → Complexity Tracking). + +- [ ] T006 Amend `.specify/memory/constitution.md`: add a "JupyterLite demo" bullet under Technology Constraints, update the Sync Impact Report comment, bump the version MINOR, and set Last Amended to today +- [ ] T007 [P] Record the demo pins (JupyterLite core, Pyodide-kernel, the kernel's bundled Pyodide, and the matplotlib the notebook installs) in the `README.md` "Pinned versions" table, noting they are independent of the frozen playground's Pyodide `0.27.7` +- [ ] T008 [P] Add a `docs/limitations.md` entry covering the demo's online-only design (no offline/air-gapped path), cold-load wheel weight, and its separate pin set (FR-010, gate 6) +- [ ] T009 Confirm the pinned toolchain installs and builds against the scaffold: `pip install -r jupyterlite/requirements.txt && jupyter lite build --config jupyterlite/jupyter_lite_config.json --output-dir jupyterlite/_output` exits 0 and produces a non-empty `_output/` + +**Checkpoint**: Toolchain builds, gate obligations satisfied — user story work can begin. + +--- + +## Phase 3: User Story 1 - See in-browser matplotlib plotting on tabular data (Priority: P1) 🎯 MVP + +**Goal**: A visitor opens the demo, opens the example notebook, runs it, and a matplotlib figure renders inline entirely in the browser from a sample dataset. + +**Independent Test**: Build locally, serve `jupyterlite/_output/`, open `demo.ipynb`, Run All → at least one matplotlib figure renders inline; verified on a cold load in Chrome and Firefox. + +### Implementation for User Story 1 + +- [ ] T010 [P] [US1] Author a generic, light, non-sensitive sample dataset at `jupyterlite/content/data/sample.csv` (a handful of columns × a few dozen rows — FR-009, research R6) +- [ ] T011 [US1] Author `jupyterlite/content/demo.ipynb`: install/import matplotlib in-browser (e.g. `piplite`/`%pip`), load `data/sample.csv`, and render ≥1 inline matplotlib figure (FR-002, FR-003; depends on T010) +- [ ] T012 [US1] Rebuild (`jupyter lite build`), serve `jupyterlite/_output/`, and confirm Run-All in the served notebook renders a matplotlib figure inline (depends on T011) +- [ ] T013 [US1] Cold-load verification: in fresh Chrome and Firefox profiles (empty cache), open the demo, Run-All, and confirm the figure renders and no server/account is involved (FR-005, SC-002, SC-003) + +**Checkpoint**: The headline value works and is testable via a local build/serve — MVP. + +--- + +## Phase 4: User Story 2 - Discover the demo from the landing page (Priority: P2) + +**Goal**: The public site welcome page shows a visible, labelled link to the JupyterLite demo. + +**Independent Test**: On the assembled welcome page, a labelled link is present and, when clicked, navigates to the `/jupyterlite/` demo. + +### Implementation for User Story 2 + +- [ ] T014 [US2] Add a visible, labelled hyperlink to `/jupyterlite/` in `web/index.html`, placed alongside the existing welcome links (FR-004, SC-001) +- [ ] T015 [US2] Verify the link in an assembled local preview (welcome page + `jupyterlite/` output under the same base) resolves to the running demo (contract G1) + +**Checkpoint**: The demo is discoverable from the site entry point. + +--- + +## Phase 5: User Story 3 - Reproduce the demo build from one documented command (Priority: P3) + +**Goal**: The demo builds from a single documented command and is published as an additive step in the existing deploy and PR-preview workflows, leaving the frozen playground untouched. + +**Independent Test**: On a clean checkout, the single documented command produces `_output/`; the workflows publish `_site/jupyterlite/` without altering the `/playground/` assembly. + +### Implementation for User Story 3 + +- [ ] T016 [US3] Document the single reproducible build command (and base-URL parameterisation) in `jupyterlite/README.md` (FR-006, contracts/build-command.md) +- [ ] T017 [US3] Add the additive JupyterLite step to `.github/workflows/deploy.yml` **after** the existing IDE copy: pinned `actions/setup-python`, `pip install -r jupyterlite/requirements.txt`, `jupyter lite build` with deploy base URL `/tabular-data-playground/jupyterlite/`, then `mkdir -p _site/jupyterlite && cp -r jupyterlite/_output/. _site/jupyterlite/` (FR-007; must not touch the pnpm IDE build, `keep_files`, or sample-package handling — FR-008) +- [ ] T018 [US3] Add the same additive step to `.github/workflows/pr-preview.yml` using the PR-scoped base URL `/tabular-data-playground/pr-preview/pr-/jupyterlite/` +- [ ] T019 [US3] Add a CI guard (in the relevant workflow) asserting `jupyter lite build` exits 0 and `_output/` is non-empty (contract C1) +- [ ] T020 [P] [US3] Extend the PR-preview comment body in `.github/workflows/pr-preview.yml` to list the new `/jupyterlite/` demo URL for reviewers + +**Checkpoint**: The demo is reproducible and published additively; the frozen playground is byte-for-byte unchanged. + +--- + +## Phase 6: Polish & Cross-Cutting Concerns + +**Purpose**: Optional hardening and final validation. + +- [ ] T021 [P] (Optional) Add a Playwright Chromium smoke that opens the built `/jupyterlite/` demo, runs the notebook, and asserts a figure element appears (research R7) +- [ ] T022 Run `specs/049-jupyterlite-demo/quickstart.md` end-to-end and confirm every listed verification step passes +- [ ] T023 [P] Confirm frozen-playground isolation: `/playground/` assets and the frozen pins in `README.md`/`app/src/pyodide/config.ts` are unchanged by this feature (FR-008, contract G4) + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)**: No dependencies — start immediately. +- **Foundational (Phase 2)**: Depends on Setup (needs the scaffold + pins to build). BLOCKS shipping and full validation of user stories. +- **User Story 1 (Phase 3)**: Depends on Foundational. The MVP. +- **User Story 2 (Phase 4)**: Depends on Foundational; independent of US1 for the link edit, but its verify step (T015) is most meaningful once a build exists (US1). +- **User Story 3 (Phase 5)**: Depends on Foundational; publishes what US1 produces. The single-command doc (T016) and CI/deploy wiring make the demo reproducible/published. +- **Polish (Phase 6)**: After the desired stories are complete. + +### User Story Dependencies + +- **US1 (P1)**: After Foundational. No dependency on other stories — testable via local build/serve. +- **US2 (P2)**: After Foundational. The `web/index.html` link edit is independent; verification benefits from US1 existing. +- **US3 (P3)**: After Foundational. Wires build/publish; independently testable by running the documented command and inspecting the assembled `_site`. + +### Within Each User Story + +- US1: sample data (T010) → notebook (T011) → local render check (T012) → cold-load check (T013). +- US3: document command (T016) → deploy wiring (T017) → preview wiring (T018) → CI guard (T019) → preview comment (T020). + +### Parallel Opportunities + +- All Phase 1 Setup tasks (T001–T005) are independent files → run in parallel. +- Foundational T007 (README) and T008 (limitations) are parallel; T006 (constitution) is a distinct file and also parallel; T009 depends on Setup. +- US1 T010 (data) can start in parallel with US2 T014 (welcome-page link) once Foundational is done. +- US3 T017 and T018 touch different workflow files (parallelizable), but T020 also edits `pr-preview.yml` so sequence T018 → T020. + +--- + +## Parallel Example: Phase 1 Setup + +```bash +# These create distinct files and can proceed together: +Task: "Create jupyterlite/ scaffold with content/ and content/data/" +Task: "Add jupyterlite/requirements.txt with pinned versions" +Task: "Add jupyterlite/jupyter_lite_config.json" +Task: "Add jupyterlite/jupyter-lite.json" +Task: "Add _output/ ignore rule" +``` + +--- + +## Implementation Strategy + +### MVP First (User Story 1) + +1. Phase 1: Setup — scaffold + pins + configs. +2. Phase 2: Foundational — constitution amendment, README pins, limitations, toolchain build check. +3. Phase 3: US1 — sample data + notebook + rendered figure, verified on cold load. +4. **STOP & VALIDATE**: the demo renders a matplotlib figure locally. This is the headline deliverable. + +### Incremental Delivery + +1. Setup + Foundational → toolchain ready and gate obligations met. +2. US1 → figure renders (MVP, locally verifiable). +3. US2 → discoverable from the welcome page. +4. US3 → reproducible single-command build wired additively into deploy + PR preview (published, reviewable). +5. Polish → optional Playwright smoke + quickstart validation + isolation check. + +--- + +## Notes + +- [P] = different files, no dependencies. +- The constitution amendment (T006), README pins (T007), and limitations entry + (T008) are **not optional** — they are the gate obligations that must land in + the same change as the dependency. +- Keep the sample data light to bound the cold-load wheel payload. +- Do not modify `app/` or the frozen `/playground/` assembly (FR-008). +- Commit after each task or logical group.