# Technical Documentation: ronreiter/interactive-tutorials

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/ronreiter/interactive-tutorials)

### Overview (README.md)

Interactive Tutorials
=====================

This is the open source repository for the free interactive tutorial websites:

* [learnpython.org](https://www.learnpython.org)
* [learnjavaonline.org](https://www.learnjavaonline.org)
* [learn-html.org](https://www.learn-html.org)
* [learn-c.org](https://www.learn-c.org)
* [learn-cpp.org](https://www.learn-cpp.org)
* [learn-js.org](https://www.learn-js.org)
* [learn-php.org](https://www.learn-php.org)
* [learnshell.org](https://www.learnshell.org)
* [learncs.org](https://www.learncs.org)
* [learn-perl.org](https://www.learn-perl.org)
* [learnrubyonline.org](https://www.learnrubyonline.org)
* [learn-golang.org](https://www.learn-golang.org)
* [learnrust.org](https://learnrust.org)

Please feel free to contribute your tutorials or exercises by sending a pull request and adding yourself on the list.

To run locally, first create a new file called `app.env` based on `app.env.example`.
(you don't need working credentials to run the website, just to execute code).

### Running with Docker

Execute:

    DEFAULT_DOMAIN=learnpython.org make run

This command will run learnpython.org website by default using Docker Compose.

To run a specific website, run with the DEFAULT_DOMAIN option set, as follows:

    DEFAULT_DOMAIN=learn-js.org make run

By default, the server process will run at http://localhost:5000.

The web server will locally compile and load all Markdown files into memory. The docker needs to be rebuilt upon any change in the Python code or the Markdown code.

### Running without Docker (locally)

To run the development server outside of a Docker, run the following command once: 

    make build-local

And then run:

    DEFAULT_DOMAIN=learn-js.org make run-local
    

### IDEOne Credentials

To make the IDEOne execution API work locally, you must obtain a username and password, 
and add the credentials to a file called app.env.
(TBD: add an option to use the remote version) 

Contributors
============
- superreg
- JBoy Advance
- qwong95
- AddaxSoft
- derco0n
- imagesid
- ...<perhaps you>...

<p>This project is supported by:</p>
<p>
  <a href="https://www.digitalocean.com/">
    <img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="201px">
  </a>
</p>

---

### Superpowers Plans 2026 08 11 Tutorial Enrichment (`docs/superpowers/plans/2026-08-11-tutorial-enrichment.md`)

# Interactive Tutorials Enrichment + learnvibecoding.org — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Enrich all 17 tutorial sites, add a learnvibecoding.org site with an in-browser WebGPU tiny model, harden `main.py`, and regenerate static multilingual translations — one PR per site plus one general PR.

**Architecture:** One Flask process per site (`main.py` + `DEFAULT_DOMAIN`). Tutorials are static Markdown at `tutorials/<site>/<language>/<Tutorial>.md`, parsed by a regex for `Tutorial / Tutorial Code / Expected Output / Solution`. Translations are static per-language folders — no runtime step. Per-site enrichment is delegated to parallel subagents following a shared playbook; the two code-heavy sub-projects (server hardening, learnvibecoding.org) are implemented inline.

**Tech Stack:** Python 3 + Flask, Markdown, pytest. learnvibecoding.org: `@huggingface/transformers` (WebGPU), `onnx-community/SmolLM2-360M-Instruct`.

## Global Constraints

- Tutorial `.md` files MUST follow the parseable format: `Tutorial` … `Exercise` … `Tutorial Code` … `Expected Output` … `Solution` heading blocks; code is indented 4 spaces.
- Expected Output assertions are EXECUTED server-side — never translate or alter `test_output_contains(...)` / `success_msg(...)` lines; only prose is translatable.
- Wiki-links use `[[Page Title]]` or `[[Page Title|display text]]`; page names are the tutorial file base-name (spaces → `_` in URLs).
- `Welcome.md` must be present in every language folder; `main.py` expects it first.
- One PR per site directory, one PR for learnvibecoding.org, one general PR for shared/server changes. All branch from `master`.
- Do NOT commit large model binaries; load the model from the HuggingFace hub at runtime in the browser.

---

### Task 1: Write the shared enrichment playbook

**Files:**
- Create: `docs/superpowers/playbooks/enrichment-playbook.md`

**Interfaces:**
- Consumes: the design spec (`docs/superpowers/specs/2026-08-11-tutorial-enrichment-design.md`).
- Produces: `docs/superpowers/playbooks/enrichment-playbook.md` — the single source of truth every per-site subagent follows. Its sections: Tutorial format (with the exact heading regex), Wiki-link rules, Welcome.md rules, Translation rules (file names match English; code + assertions untouched; only prose translated), Quality bar checklist, Site structure conventions.

- [ ] **Step 1: Write the playbook file** — author the sections listed above with concrete examples pulled from `tutorials/learnpython.org/en/Hello, World!.md`.
- [ ] **Step 2: Commit**

```bash
git add docs/superpowers/playbooks/enrichment-playbook.md
git commit -m "docs: add tutorial enrichment playbook"
```

---

### Task 2: Fix malformed/un-wired sites (general branch)

**Files:**
- Modify: `constants.py` (add `LEARNRUST_DOMAIN`, `LEARNASSEMBLY_DOMAIN`, `LEARNSOLIDITY_DOMAIN` + `DOMAIN_DATA` entries)
- Modify: `main.py` (skip malformed domain dirs gracefully)
- Rename: `tutorials/learnRust.org/` → `tutorials/learnrust.org/` (move `Hello World!.md`, `Welcome.md` into `tutorials/learnrust.org/en/`)
- Test: `tests/test_domain_loading.py`

**Interfaces:**
- Consumes: `constants.DOMAIN_DATA` structure (see `learnpython.org` entry).
- Produces: three new `DOMAIN_DATA` keys (`learnrust.org`, `learnassembly.org`, `learnsolidity.org`) that `init_tutorials()` can serve.

- [ ] **Step 1: Write the failing test**

```python
import constants

def test_unsupported_domains_wired():
    for d in ["learnrust.org", "learnassembly.org", "learnsolidity.org"]:
        assert d in constants.DOMAIN_DATA
```

- [ ] **Step 2: Run to verify it fails** — `pytest tests/test_domain_loading.py -v` → FAIL.
- [ ] **Step 3: Add domain constants + DOMAIN_DATA entries** — for each: `language`, `language_id` (any, unused), `codemirror_mode`, `prism_mode`, `default_code` (a stub print), `namespace`, `full_url`, logos/favicon under `static/` (reuse a generic placeholder), `contact_email`/`support_email`.
- [ ] **Step 4: Rename learnRust folder** — `git mv` `tutorials/learnRust.org` → `tutorials/learnrust.org`, then move `.md` files into `tutorials/learnrust.org/en/`.
- [ ] **Step 5: Harden `init_tutorials`** — wrap the `tutorials.remove("Welcome.md")` and per-file logic so a domain with only root-level `.md` (not `en/`) logs a warning instead of crashing; skip gracefully.
- [ ] **Step 6: Run test to verify it passes** — `pytest tests/test_domain_loading.py -v` → PASS.
- [ ] **Step 7: Commit**

```bash
git add constants.py main.py tests/ tutorials/learnrust.org
git commit -m "fix: wire up learnrust/learnassembly/learnsolidity, harden domain loading"
```

---

### Task 3: Harden `main.py` (general branch)

**Files:**
- Modify: `main.py` (`cgi.escape` → `html.escape`; `signin`/`signup` stub; `error404` template context)
- Test: `tests/test_main.py`

**Interfaces:**
- Consumes: `Flask`, existing template `error404.html`.
- Produces: `signin`/`signup` routes return JSON `{"status": "error", "error": "not_implemented"}` with 501 instead of crashing on the undefined `users` object.

- [ ] **Step 1: Write the failing test**

```python
import json

def test_signin_returns_501(client):
    r = client.get("/signin?email=a@b.com")
    assert r.status_code == 501
    assert json.loads(r.data)["status"] == "error"
```

- [ ] **Step 2: Run to verify it fails** — FAIL (`users` undefined → 500).
- [ ] **Step 3: Implement** — replace the `users.findOne(...)` / `users.insert(...)` bodies with a guarded `not_implemented` 501 response; replace `cgi.escape` with `html.escape` (import `html`).
- [ ] **Step 4: Run to verify it passes** — PASS.
- [ ] **Step 5: Commit**

```bash
git add main.py tests/test_main.py
git commit -m "fix: harden main.py — stub signin/signup, drop deprecated cgi"
```

---

### Task 4: Add tests harness for tutorial parsing & translations

**Files:**
- Create: `tests/test_tutorial_parsing.py`, `tests/test_translations.py`, `tests/conftest.py`
- Modify: `pyproject.toml` (add pytest dev-dependency)

**Interfaces:**
- Consumes: `main.init_tutorials`, `main.wikify`, `main.untab`.
- Produces: assertions that every language folder has a `Welcome.md`, that every `.md` in `en/` has a matching file in each non-en language folder, and that `main.sections` parses a valid tutorial.

- [ ] **Step 1: Write tests**

```python
import os, main

TUT = "tutorials"

def test_every_language_has_welcome():
    for domain in os.listdir(TUT):
        for lang in os.listdir(os.path.join(TUT, domain)):
            if os.path.isdir(os.path.join(TUT, domain, lang)):
                assert "Welcome.md" in os.listdir(os.path.join(TUT, domain, lang))

def test_en_files_have_translations():
    for domain in os.listdir(TUT):
        dpath = os.path.join(TUT, domain)
        if not os.path.isdir(dpath): continue
        en_files = set(os.listdir(os.path.join(dpath, "en")))
        for lang in os.listdir(dpath):
            if lang == "en" or not os.path.isdir(os.path.join(dpath, lang)): continue
            missing = en_files - set(os.listdir(os.path.join(dpath, lang)))
            assert not missing, f"{domain}/{lang} missing {missing}"
```

- [ ] **Step 2: Run to verify they fail** — confirm current gaps are detected (translation folders are incomplete today).
- [ ] **Step 3: Commit tests**

```bash
git add tests/ pyproject.toml
git commit -m "test: add parsing + translation coverage harness"
```

---

### Task 5: learnvibecoding.org — domain wiring + custom template (own branch)

**Files:**
- Modify: `constants.py` (add `LEARNVIBECODING_DOMAIN` + `DOMAIN_DATA` entry with `is_browser_model: True`)
- Modify: `main.py` (route selects `index-vibecoding.html` when `domain_data["is_browser_model"]`)
- Create: `templates/index-vibecoding.html`, `static/js/vibecoding.js`, `static/css/vibecoding.css`
- Create: `tutorials/learnvibecoding.org/en/{Welcome, Prompting, Iterating, Shipping}.md`
- Modify: `Makefile`, `website_docker.conf` (deploy wiring)

**Interfaces:**
- Consumes: `constants.DOMAIN_DATA[domain]["is_browser_model"]`.
- Produces: a site where the tutorial page embeds a transformers.js WebGPU runner that loads `onnx-community/SmolLM2-360M-Instruct` from the HF hub and generates text from a prompt box.

- [ ] **Step 1: Add `learnvibecoding.org` to DOMAIN_DATA** with `language: "text"`, `default_code: ""`, `is_browser_model: True`, placeholder logos/favicon.
- [ ] **Step 2: Route branch in `index()`** — `"index-vibecoding.html" if domain_data.get("is_browser_model") else "index.html"`.
- [ ] **Step 3: Write `templates/index-vibecoding.html`** — extend `base.html`; include a prompt textarea, Run button, output `<pre>`, progress bar for model download; load `static/js/vibecoding.js`; disable the IDEOne "Run code" button.
- [ ] **Step 4: Write `static/js/vibecoding.js`** — `import { pipeline } from "@huggingface/transformers"` via an ESM import map; `const gen = await pipeline("text-generation", "onnx-community/SmolLM2-360M-Instruct", { device: "webgpu" })`; on Run: `await gen(prompt, { max_new_tokens: 200 })`; render output; catch WebGPU-unavailable error and show a friendly fallback.
- [ ] **Step 5: Write the 4 tutorials** per the playbook (no IDEOne execution; Expected Output section documents expected generated behavior in prose).
- [ ] **Step 6: Add deploy wiring** — Makefile target `run-vibecoding`; `website_docker.conf` note.
- [ ] **Step 7: Verify locally** — `DEFAULT_DOMAIN=learnvibecoding.org make build-local` and run; confirm the page loads and model code path is wired. Then commit.

```bash
git add constants.py main.py templates/index-vibecoding.html static/js/vibecoding.js static/css/vibecoding.css tutorials/learnvibecoding.org Makefile website_docker.conf
git commit -m "feat: add learnvibecoding.org with in-browser WebGPU tiny model"
```

---

### Task 6: Per-site enrichment orchestration (17 site branches → PRs)

**Files:**
- Per-site branch: `enrich-<site>`, touching only `tutorials/<site>/` + `courses.json` (if needed).

**Interfaces:**
- Consumes: the playbook (Task 1), the translation harness (Task 4) for verification.
- Produces: one branch + one PR per site with enriched English tutorials and regenerated translations in all existing language folders.

- [ ] **Step 1: Dispatch one subagent per site** (in parallel, `isolation: "worktree"`) — each instructed to: read the playbook; audit the site's `en/` tutorials; add missing popular topics/exercises; improve weak chapters; fix wiki-links; ensure `Welcome.md`; regenerate every non-en language folder from the enriched English set (only prose translated; code + assertions untouched); commit on `enrich-<site>`.
- [ ] **Step 2: Verification pass** — run `tests/test_translations.py` + `main.py` load for each site; fix gaps.
- [ ] **Step 3: Create PR per site** — `gh pr create` from each `enrich-<site>` branch → `master`, one per site.

---

### Task 7: General improvements PR

**Files:** the `general-server-hardening` branch (design doc + Tasks 2–4 changes).

- [ ] **Step 1: Run full test suite** — `pytest tests/ -v`; all pass.
- [ ] **Step 2: Create PR** — `gh pr create` from `general-server-hardening` → `master`.

---

## Self-Review Notes

- Spec coverage: per-site enrichment (Task 6), learnvibecoding (Task 5), main.py hardening (Tasks 2–3), translations (Task 6 + harness Task 4), deploy wiring (Task 5 step 6), PR strategy (Tasks 6–7).
- No placeholders; code blocks are concrete.
- Type consistency: `is_browser_model` flag introduced in Task 5 and consumed by the same task's route branch — no cross-task drift.

---

### Superpowers Playbooks Enrichment Playbook (`docs/superpowers/playbooks/enrichment-playbook.md`)

# Tutorial Enrichment Playbook

This is the single source of truth for every per-site enrichment subagent. Follow it
exactly. When enriching `tutorials/<site>/`, every change must conform to these rules.

## 1. Tutorial file format

Every tutorial is a Markdown file `tutorials/<site>/<language>/<Name>.md` whose body is
parsed by `main.py` via this regex:

```
Tutorial\n[=\-]+\n+(.*)\n*Tutorial Code\n[=\-]+\n+(.*)\n*Expected Output\n[=\-]+\n+(.*)\n*Solution\n[=\-]+\n*(.*)\n*
```

So a valid tutorial MUST have these heading blocks, in order:

```
Tutorial
--------
<intro prose; code examples indented 4 spaces>

Exercise
--------
<the exercise prompt>

Tutorial Code
-------------
<starter code, indented 4 spaces>

Expected Output
---------------
<test assertions; see §3>

Solution
--------
<correct code, indented 4 spaces>
```

- Code blocks are indented with **4 spaces** (markdown code). `main.py`'s `untab()`
  strips 4 leading spaces to produce the runnable code.
- A file WITHOUT these blocks is treated as a non-tutorial page (only `Welcome.md` is
  expected to be a non-tutorial page). Do not leave regular tutorials un-parsed.

## 2. Wiki-links

- Links use `[[Page Title]]` or `[[Page Title|display text]]`.
- The page name is the **exact tutorial file base-name** (minus `.md`). URLs replace
  spaces with `_` at render time, so keep the file name as the canonical key.
- `Welcome.md` lists chapters as bulleted `- [[Chapter Name]]` items under `###` headings.
- Any tutorial name referenced in a wiki-link must have a matching `.md` file in that
  language folder, or `main.py` creates an empty stub page (acceptable only for
  genuinely-orphan links; prefer real files).

## 3. Expected Output — two conventions (IMPORTANT)

There are TWO different conventions across sites. Identify which one a site uses by
reading an existing tutorial's `Expected Output` block, then follow that site's
convention.

### A) Checker DSL (learnpython.org only)

learnpython.org uses a checker DSL inside `Expected Output`:

```
test_output_contains("some string")
success_msg('Great job!')
```

Other checker functions seen: `test_object`, `test_objects`, `error_contains`,
`output_contains`. These lines are code, NOT literal output:
- NEVER alter them.
- In translations, keep them byte-for-byte identical to English.

### B) Literal output (ALL other sites: learn-js, learn-ts, learn-c, learn-html,
learnsqlonline, learnjavaonline, learn-cpp, learncs, learnshell, learn-php,
learnrubyonline, learn-perl, learn-golang, and the rest)

The `Expected Output` block is the **literal expected output string** that the
program must print, compared by exact trimmed string equality (the JS `print()`
function does `$.trim(tutorialData.output) === $.trim(text)`).

- When you WRITE a new tutorial, put the exact literal output the starter code /
  solution produces (e.g. for `print("Hello")` the Expected Output is `Hello`).
- Keep code, literal output, and whitespace EXACTLY matching what running the
  solution produces — the "correct" check depends on it.
- In translations, keep the literal output string byte-for-byte identical to English.

> Note: learn-html.org compares rendered HTML structurally (not string equality);
> the Expected Output there is the expected full HTML document.

## 4. Welcome.md

- Every language folder MUST have `Welcome.md`. `main.py` removes it from the list and
  places it first.
- Format: `# Welcome` heading, intro prose (may include raw HTML `<br>`, `<a>`), then
  `### Section` headings with bulleted `- [[Chapter]]` lists.

## 5. Translation rules (static per-language folders)

- Each language is its own folder of translated `.md` files. There is NO runtime step.
- A language folder is in sync when it has a file with the SAME base-name for every
  `en/` file, and NO extra files.
- When translating:
  - Keep the **file name identical** to English.
  - Keep all code blocks (indented 4-space lines) byte-for-byte identical.
  - Keep `Expected Output` assertion lines identical.
  - Keep wiki-link targets identical (`[[Same Name]]`) — page names are the English names.
  - Translate only prose (intro, exercise prompt, inline `inline code` labels if any).

## 6. Enrichment quality bar

For each site:
- **Audit**: list every `en/` tutorial; identify missing popular topics for that language.
- **Add** missing topics as full tutorials (format in §1), with real examples, an
  exercise, assertions, and a solution.
- **Improve** weak chapters: fix typos, add missing examples, complete the exercise.
- **Fix** wiki-links so every linked page exists.
- **Regenerate** all non-English language folders from the enriched English set per §5.
- **Preserve** the existing `default_code` / editor-mode conventions already in
  `constants.DOMAIN_DATA[site]` — do not change editor config unless required.

## 7. Branch / commit rules

- Work on the assigned branch (`enrich-<site>`), touching ONLY `tutorials/<site>/` and
  (if needed) `courses.json`.
- Commit with a clear message; one commit per logical change; end with
  `Co-Authored-By: Claude <noreply@anthropic.com>`.
- Do not commit model binaries, logs, or local artifacts.

---

### Superpowers Specs 2026 08 11 Tutorial Enrichment Design (`docs/superpowers/specs/2026-08-11-tutorial-enrichment-design.md`)

# Design: Interactive Tutorials Enrichment + learnvibecoding.org

Date: 2026-08-11
Status: Approved (user granted autonomous execution; committed for review)

## 1. Overview

Enrich all 17 tutorial sites in `tutorials/`, add a new `learnvibecoding.org` site that
runs a tiny model in the browser (WebGPU), harden `main.py`, and regenerate static
multilingual translations. One pull request per site, plus one general PR for
server/shared improvements.

## 2. Architecture context (as-is)

- One Flask process per site (`main.py` + `DEFAULT_DOMAIN` env), gunicorn in production.
- Tutorials are static Markdown per site per language:
  `tutorials/<site>/<language>/<Tutorial>.md`, parsed by a regex in `main.py`
  (`Tutorial / Tutorial Code / Expected Output / Solution`).
- Translations are STATIC: each language is its own folder of translated `.md` files.
  There is no runtime or generation script today.
- `constants.py` `DOMAIN_DATA` wires each site to IDEOne (remote compile), editor modes,
  logos, analytics. `courses.json` holds per-site sponsored course listings.
- `templates/index.html` renders tutorials; `index-python.html` is the Python-specific variant.

### Findings that shape the plan
- 17 site folders; coverage ranges from learn-html (69) / learn-ts (43) down to
  learnassembly (1), learnscala (2), learnsolidity (2).
- `learnRust.org` is malformed: `.md` files sit in the root instead of `en/`.
- `learnassembly.org`, `learnRust.org`, `learnsolidity.org` are not wired into
  `DOMAIN_DATA` and are skipped at load.
- `main.py` bugs: `signin`/`signup` reference an undefined `users` object; `cgi` is
  deprecated (Python 3.13+).

## 3. Approach: central playbook + parallel subagents

One shared enrichment playbook defines the quality bar (tutorial format, wiki-link
conventions, translation rules). One subagent per site follows it on its own git branch
→ one PR per site. A verification/integration pass happens before each PR is created.

## 4. Sub-project 1 — Per-site enrichment (17 PRs)

Per site: review the existing tutorial set; fill missing popular topics/exercises;
improve weak chapters; fix `[[wiki-link]]` conventions; ensure a proper `Welcome.md`;
regenerate that site's non-English language folders from the enriched English set.

Structural fixes folded into the affected site PRs:
- Fix malformed `learnRust.org` (move files to `en/`).
- Wire `learnassembly.org`, `learnsolidity.org`, `learnrust.org` into `DOMAIN_DATA`.

## 5. Sub-project 2 — learnvibecoding.org (1 PR)

- New site. **WebGPU-only tiny model** via `@huggingface/transformers`, running
  `onnx-community/SmolLM2-360M-Instruct` (ONNX). Small enough to be "tiny", capable
  enough for a vibe-coding demo.
- Custom template: prompt box → in-browser generation → code/response output.
  Chapters teach prompting, iteration, and shipping a small app.
- Static model assets + JS runner under `static/`, new `DOMAIN_DATA` entry, custom
  template, plus gunicorn/nginx/Docker/Makefile deploy wiring.

## 6. Sub-project 3 — main.py hardening (1 general PR)

- Fix undefined `users` object.
- Harden domain loading / malformed-folder handling.
- Modernize deprecated `cgi` (use `html` module).
- Add basic tests (tutorial parsing, wiki-link resolution, translation folders).
- Include this design doc in this PR.

## 7. Sub-project 4 — Translations

Static regeneration into ALL existing language folders per site, folded into each
site's PR. No runtime translation step.

## 8. PR / branch strategy

- One branch + PR per site (17), one branch + PR for `learnvibecoding.org` (1), one
  branch + PR for general server improvements (1). All branch from `master`.

## 9. Verification

- Run `main.py` locally per domain (or `make build-local`) and confirm tutorial parsing
  loads without "not a tutorial" warnings.
- Validate wiki-links resolve; confirm translation folders parse; run added tests.

---

--- METRICS ---
- Files Extracted: 4
- Estimated Token Budget: ~5883 tokens
- Canonical Reference: https://codewiki.google/github.com/ronreiter/interactive-tutorials
