A 100%-Tish, in-browser learn-to-code platform. Forty beginner chapters, twenty-eight Tish-deep chapters, and twenty-one capstone-project chapters across six builds — all running in your browser tab.
Same spirit as
tish-playground: the compiler, the VM, the lessons, and the live code execution all run on your machine, in your browser, with no backend involved.
The in-browser stack is pure Tish (Lattish + panels + tailwind emitter + browser-server). There is no React, Webpack, or Tailwind CLI.
npm run dev / npm run build are thin wrappers around just dev and just build (see justfile); you can call just directly if you prefer.
public/boot.js is plain ESM (not compiled Tish): it loads wasm + import() of learn.js vs learn_sandbox.js, and can defer heavy work when prerendered pages set <meta name="ll-defer-wasm" content="1">.
Static HTML for each lesson is emitted by tish run --feature fs prerender.tish — same Tishdoc parser and highlighter path as the app (see app/tishdoc.tish deep import of TishHighlight.tish).
CSS is still generated by tish run --feature fs build-css.tish via the pure-Tish tish-tailwind emitter.
Even the "server-shaped" capstones — real-time chat, REST API, blog generator — run entirely in your tab. No relay to deploy, no tish run server.tish on your machine, no terminal. The trick is tish-browser-server, a pure-Tish shim that mirrors the 'http' / 'fs' / 'process' API surface over a Service Worker, BroadcastChannel, and IndexedDB.
Each capstone closes with a "Take it real" appendix showing the one-line diff to swap tish-browser-server for the host modules and run the same code on a deployable Tish server.
| Track | Chapters | Audience |
|---|---|---|
| Beginner — Learn to code | 40 + 6 best-practice interludes | From zero. Starts with no-typing chip-tap puzzles; ends with a static-site export. |
| Tish, in depth | 28 across 5 parts | If you already code. Mirrors LANGUAGE.md. |
| Capstones | 21 across 6 projects | Real-time chat, Snake, blog generator, REST API, collaborative whiteboard, tiny game engine. |
Prereqs: Rust + just + wasm-bindgen-cli, and a sibling checkout of tish (default ../tish, override with TISH_ROOT).
npm install # one-time: sibling packages (lattish, panels, tailwind, browser-server)
npm run dev # → just dev → full build + dev-server.tish (:8765)
npm run build # → just build (includes prerender + sandbox split)
just dev # same as npm run devTargets:
| Just target | Builds |
|---|---|
just build-app |
learn.js + deps: lattish-runtime.js, learn_sandbox.js |
just build-sandbox |
app/main_sandbox.tish → learn_sandbox.js (standalone /sandbox route) |
just build-prerender |
prerender.tish → public/<track>/<chapter>/index.html per lesson |
just build-runtime |
Lattish runtime → public/dist/lattish-runtime.js (used in iframe sandboxes) |
just build-sw |
tish-browser-server's SW → public/dist/tish-sw.js |
just build-css |
tish-tailwind scans app/** + content/** → public/utilities.css |
just build-vm |
tishlang_wasm_runtime Rust crate → tish_vm.{js,wasm} |
just build-compiler |
tishlang_compiler_wasm Rust crate → tish_compiler.{js,wasm} |
just build |
All of the above |
just quick |
App + sandbox + CSS + prerender (skips Rust builds) |
just dev |
just build + serve via dev-server.tish |
learn.config.json at the repo root configures contentDir, tracksDir, outDir, and basePath for prerender.tish (reserved fields documented in that file’s comments if needed).
tish-learn/
app/ # 100% Tish UI sources
main.tish # createRoot → LearnApp
LearnApp.tish # header, sidebar, content
router.tish # History-API router /<track>/<chapter>
content_loader.tish # fetches /content/, caches
progress.tish # localStorage completion state
tishdoc.tish # MDX-style parser (TishDoc subset)
views/
TrackPicker.tish # 3-track home + per-track index
LessonView.tish # parse + walk + dispatch directives
directives/
Pick.tish # ::pick — Duolingo chip-tap
TryIt.tish # ::tryit — MiniRunner without expected
Exercise.tish # :::exercise — MiniRunner with expected
Quiz.tish # :::quiz radio
Sandbox.tish # :::sandbox{kind=console|ide}
Callout.tish # :::callout{kind=info|tip|warn|danger|note}
Project.tish # :::project — capstone hero
utils/
objects.tish # opt(o, key, fb), has(o, key), isFileError(v)
content/
tracks/{beginner,tish-deep,capstones}.json
beginner/ # 46 chapters
tish-deep/ # 28 chapters
capstones/{chat,snake,blog,rest-api,whiteboard,game-engine}/
public/
index.html # shell → boot.js (wasm + route split + optional defer)
boot.js # loads vm/compiler + learn.js or learn_sandbox.js
base.css # hand-written semantic styles
utilities.css # generated by tish-tailwind
dist/ # learn.js + lattish-runtime.js + tish_vm.{js,wasm} + tish_compiler.{js,wasm} + tish-sw.js
build-css.tish # CSS pipeline
dev-server.tish # static file server
justfile # build recipes
Only this is JS at runtime:
dist/learn.js— output oftish build --target js app/main.tish. Authored as Tish.dist/lattish-runtime.js— output oftish buildon the Lattish source.dist/tish-sw.js— output oftish buildontish-browser-server/src/sw_worker.tish.dist/tish_vm.{js,wasm}+dist/tish_compiler.{js,wasm}— auto-generated wasm-bindgen glue from Rust crates. We don't write these.public/index.html+public/boot.js— load wasm-bindgen modules, expose__tish*globals, thenimport()dist/learn.js(main app) ordist/learn_sandbox.json/sandbox. Prerendered chapter HTML adds<meta name="ll-defer-wasm" content="1">so the same boot can defer work until idle or first input.
End-to-end smoke checks performed:
- ✅
tish build app/main.tish --target jsproduces a working bundle (~5,100 lines). - ✅
tish build node_modules/lattish/src/Lattish.tish --target jsproduces the iframe runtime. - ✅
tish build node_modules/tish-browser-server/src/sw_worker.tish --target jsproduces the SW. - ✅
tish run --feature fs --feature http build-css.tishwritespublic/utilities.css(210 lines, 23/310 utilities used). - ✅ Dev server boots on
:8765and serves:index.html,/content/tracks/*.json,/content/<track>/<chapter>.tishdoc.md. - ✅
tish-playgroundstill builds end-to-end after consumingtish-ide-panels. - ✅
tish-audiobuilds via the new pure-Tishtish-tailwindemitter (scripts/build-css.tish). - ✅ Audit:
rg "(tailwindcss|postcss|autoprefixer|esbuild|webpack|react)"across every newpackage.jsonreturns zero matches. - ✅ Audit: no
.js/.jsx/.ts/.tsxfiles inapp/orsrc/— only.tishand assets.
- Part I — Hello, code (4): print, errors, variables, constants
- Part II — Numbers, words, choices (7): numbers, strings, methods, booleans, if/else, ternary, logical ops
- Part III — Repetition and collections (8): while, for, break/continue, arrays, methods, iterating, map/filter/reduce
- Part IV — Objects and functions (7): objects, iter, functions, arrows, scope, closures
- Part V — Real-world I/O (7): try/catch, JSON, modules, console tools, reading docs
- Part VI — Your first webpage (4): JSX, forms, lists, localStorage
- Part VII — Useful real apps (7): fetch, canvas, Snake, two-tab chat, virtual fs, static-site export, what's next
- BP-01 to BP-06 interspersed: read errors, name things well, console.log to debug, smaller pieces, comment why, run early/often
- I Orientation (4): why-tish, tour, vs-javascript, blocks/indent
- II Core (6): scope, functions, control flow, strings/numbers, arrays/objects, new/construct
- III Async/Errors/I/O (8): promises, async/await, http client, http server, fs, process, regex, feature flags
- IV Modules and types (4): imports, cargo:, type annotations, type/declare
- V UI and native compile (6): JSX/Lattish, native macOS, native modules, build targets, tooling, deploy
- C1 Real-time chat (3) — BroadcastChannel + take-it-real
- C2 Snake (3) — canvas, input, juice + persistent score
- C3 Static blog generator (4) — markdown + virtual fs + templates + zip download
- C4 Tiny REST API (3) — Service-Worker-backed CRUD
- C5 Collaborative whiteboard (3) — multi-tab sync + WebRTC bonus
- C6 Tiny game engine (5) — ECS + sprites + AABB + scenes + ship a platformer
tish-playground— the IDE panels intish-ide-panelsare extracted from here.tish-audio— the Tailwind utility table intish-tailwindis extracted fromtish-audio/tish-tailwind.tish-creator— the TishDoc directive shape (:::name{...}/::name{...}) follows the parser used there.- LANGUAGE.md — the canonical language spec the curriculum teaches.