Playground & tutorial
Status: building — a first static WASM playground is live
(ComlineProject/playground#1):
wasm/ (comline-playground-wasm — wasm-bindgen over the real comline-core +
comline-codegen + the rust / typescript generators) and app/ (Vite +
vanilla TS, the WASM in a Web Worker), deploying to GitHub Pages. It covers the
compile → diagnostics → IR → codegen loop for one schema; the stale early-2024
SvelteKit scaffold was dropped. The runtime demo and multi-file support are the
open work below · Affects ComlineProject/playground
Goal
A browser experience where you type a schema and, live:
- see the IR and the validation diagnostics (inline, LSP-style);
- pick a target language + mode and see the generated code;
- run a runtime demo — send a message through the call system to a handler and watch it serialise, route, dispatch, and come back.
Two properties are non-negotiable:
- Correctness — the playground runs the same code as the CLI, not a re-implementation. Diagnostics and generated output are byte-identical.
- Responsiveness — the edit → IR → diagnostics → code loop keeps up with typing (target < ~1 frame of jank on the editor thread).
The target is everything — compile + diagnostics + codegen + a working
runtime demo; there is no cut-down "just the editor" first release. The
compile/codegen loop is language-agnostic from the outset (it is core +
comline-codelib-gen in WASM); the runtime demo fills in per language by how
runnable that language is in a browser (see
Running handlers per language).
The core decision: WASM vs a compile server
| client-side WASM | compile-queue server | |
|---|---|---|
| latency | instant (local) | network round-trip + queue |
| offline | yes | no |
| hosting | static, free | a service to run and scale |
can run cargo build (real lib / dylib, native runtime) |
no | yes |
| one source of truth with the CLI | yes (same crates) | yes (same binary) |
core (parse → IR → validate) and comline-codelib-gen (codegen) are the
real-time loop, and both are WASM-friendly: pure Rust, once_cell / eyre, and
a pure-Rust tree-sitter runtime (rust-sitter → tree-sitter-c2rust). No
std::fs, threads, or libloading on that path.
Recommendation: hybrid.
- WASM for the hot loop —
core+comline-codelib-gencompiled towasm32-unknown-unknownviawasm-bindgen, run in a Web Worker so the editor thread never blocks. This coverscodemode for every language and emitting thelib-mode file tree. - The
compilation-queue-server, later and optional — only for what WASM genuinely can't do:cargo buildof a generated crate, a real multi-process runtime demo. The editor / diagnostics / codegen experience does not depend on it.
The runtime demo
The concrete, now-buildable plan for this is Playground simulation — Phase 1: two protocol instances on a canvas, wired over
@comline/runtime's in-memoryduplex(), with a frame inspector. It drives the frozen IR through the real runtime — no codegen or transpile on the path — so acomline-corechange is not needed beyond one read-only WASM function.
The full runtime is transport + call framing + routing + dispatch. In a browser:
- serialisation (
rmp-serde/serde_json), call framing (JSON-RPC), in-process routing — all WASM-able. - real sockets — no. Use an in-memory loopback: client → a fake "wire" →
runtime → handler, all in one context, where the wire is an inspectable queue
you can pause and delay to visualise latency, ordering, and back-pressure
(this is what the
tutorialREADME's "ping, requests/responses counts, graphs, simulate machines" asks for). - the loopback needs a runtime shim in the target language (pure that language, speaking the loopback protocol) plus a way to run the user's handler in that language.
Running handlers per language
Whether a handler can run in the browser depends entirely on the target language. Two families:
- VM / interpreted — ship the language's VM as WASM (or use the browser's own), run the handler and a pure-language shim directly. No compiler needed.
- Compiled — the handler has to be compiled first. In-browser compilers for
these are either enormous or immature, so they are compile-server work
(revive
compilation-queue-server), not client-side.
| Language | Handler in browser via | Approx. payload | Shim |
|---|---|---|---|
| JavaScript | native | — | pure JS |
| TypeScript | esbuild-wasm / swc transpile, then run |
~1–3 MB | pure TS |
| Lua 5.4 | wasmoon (Lua→WASM) |
~200 KB | pure Lua |
| Luau | emscripten build of Luau | ~1–2 MB | pure Luau |
| Python 3.11+ | Pyodide (CPython→WASM) | ~6–10 MB core | pure Python |
| C | tcc.wasm (limited) or wasm-clang (~25–30 MB) |
small–huge | C→WASM, JS calls exports |
| Rust / C++ / Go | no practical in-browser compiler | — | compile server |
Rollout order follows payload and maturity: JS/TS → Lua → Luau → Python client-side; Rust / C / Go via the server. The compile/diagnostics/codegen loop is unaffected — it works for every target from the start.
This is the piece to research first. The core side is
settled: it builds for wasm32 and the bundle is ~163 KB
gzipped. What's left is the handlers — measure the real Pyodide / wasmoon /
Luau-wasm / esbuild-wasm payloads on a warm cache and check tcc.wasm's
language-feature ceiling. The matrix above is the hypothesis, not a verified
result.
The "same as the CLI" contract
- One
comline-playgroundcrate (inweb/orgeneration) depends oncomline-core+comline-codelib-genand exposes a smallwasm-bindgensurface: compile, thengenerate(target, mode). - Entry point:
comline_core::package::build::PackageSources(core#42) — the filesystem-free twin ofcompile_package..config(src)(optional; a minimal congregation is synthesised) +.schema(namespace_segments, src)per editor tab +.compile() -> ProjectContext. Same interpretation + validation pass as the CLI, so diagnostics and IR match. - Reuse
render_validation_errorand the diagnostics module so errors render exactly as the CLI prints them. - Pin the crate revisions and show them in the UI — a playground bug report is
then reproducible against a known
core/generation.
Performance notes
- Web Worker for the WASM;
postMessagethe source in, structured diagnostics + files out. - Debounce recompiles ~150–250 ms; cancel the in-flight worker call on a new keystroke.
- Exploit
core'sIncrementalInterpreterfor cheap reparse. - WASM size — the tree-sitter grammar tables dominate. Measure; lazy-load the module on first edit; cache it under a version-stamped URL (Cache API) so a revisit is warm.
wasm-opt,opt-level = "z"vs"s", and SIMD are worth trying once there is a size number to move.
What's built (playground#1)
A statically-deployable playground, matching the recommendation above:
wasm/—comline-playground-wasm, awasm-bindgencrate overcomline-core+comline-codegen+comline-codegen-rust/-typescriptandcomline-language-server(git rev; the LSP crate withdefault-features = false, so notower-lsp— it builds for wasm32).compile(source) -> { ok, diagnostics, ir, units }runs the LSP's ownall_diagnostics(parse errors +comline-corevalidation);generate(source, target, mode) -> { files, error };semantic_tokens/hover/completionsare the LSP handlers verbatim.app/— Vite; a CodeMirror 6 editor whose highlighting (token decorations), diagnostics (linter), hover and autocomplete are all fed from the WASM — i.e. the same codecomline-lspruns. The WASM is in a Web Worker. Panels: the frozen-IR dump and the generated-code view (rust / typescript × code / lib).base: "./"for/<repo>/.- deploy — a workflow builds the WASM with
wasm-pack, builds the site with Vite, publishesapp/distto Pages; runs (build only) on PRs too.
This is the no-duplication shape from the near-term note below: the browser
editor and comline-vscode both consume one Rust analysis layer
(comline-language-server). Highlighting is its semantic_tokens handler — no
separate Lezer / TextMate grammar to keep in sync.
Not yet: multi-file packages, config (config.idp / comline.toml) input, the
runtime demo, docs embedding.
Near-term: the language server
ComlineProject/language-server (comline-lsp, tower-lsp + comline-core)
gives the same edit → parse → diagnostics loop in a real editor today, no
WASM or SvelteKit needed — the faster manual-test vehicle while the playground
comes together. It parses .ids with comline-core's grammar and provides
diagnostics, an outline, hover, go-to-definition and find-references; completion,
rename, formatting and semantic-tokens handlers exist but aren't all wired into
backend.rs yet.
Revived against current core and pinned by git rev like the rest of the tree
(language-server#1); it shares comline-core with the eventual WASM path, so
work on the analysis layer benefits both.
Playground vs tutorial
- Playground — a free-form scratchpad. Shareable: encode the editor state in the URL (with a size ceiling), or a tiny gist-like store if that ceiling bites.
- Tutorial — guided, step-gated lessons, each a pre-filled editor + an
expected-output panel, building up structs → enums → protocols → errors →
validators → imports → generate → runtime. The
tutorialREADME wants each step to simulate running machines with distinct local/remote panels and live stats — the loopback runtime above is the engine for that. - Both consume the same WASM module and the same component library.
Where it lives / embedding
SvelteKit for the apps, framework-neutral for what's shared. The
playground and tutorial stay SvelteKit (the existing scaffold). Below them:
- a neutral TS package — the WASM module + its JS wrapper (
parse,validate,generate,runLoopback), the per-language handler runners (Pyodide / wasmoon / esbuild adapters), the loopback "wire" engine, and the diagnostics / IR types. No framework. (This is whatweb/shared/shared-compileandshared-compilationwere reaching for.) - a Svelte component library (
shared-components) for the editor, the IR / code / message-flow panels, and the stats graphs — consumed by both apps.
For docs embedding: the docs site is Zensical (static, no Svelte runtime).
Compile the playground shell as a custom element (<svelte:options
customElement>) so a guide / tutorial page can <script src> it and drop
<comline-playground schema="..."> inline — no iframe, shares the page's theme.
An iframe of the SvelteKit playground is the fallback if the custom-element path
gets fiddly.
To decide: one deploy or three (docs / playground / tutorial); one domain or subdomains.
Decided
- Target — everything: compile + diagnostics + codegen + a runtime demo. No cut-down first release; the runtime demo simply fills in per language.
- Framework — SvelteKit for
playgroundandtutorial; a neutral TS package for the WASM/loopback/runner core; a Svelte component library, also built as custom elements for the docs.
Findings so far
comline-core + comline-codelib-gen build for wasm32-unknown-unknown and
the bundle is small — verified end to end:
- The normal dependency tree is C-free and browser-safe:
rust-sitter→tree-sitter-c2rust(pure Rust), notokio/libloading/abi_stable. comline-core'sbuild.rs(viarust-sitter-tool0.4.5) already targets wasm32 — it writes a minimalwasm-sysroot(stdint.h/stdlib.h/stdio.h/stdbool.h) so the generated tree-sitterparser.ccross-compiles. The one build requirement isclangfor that C step (trivial in CI).- A
cdylibsize probe —opt-level = "z"+ LTO +strip+panic = "abort", exercising parse → IR → validate → diagnostics → codegen (rust + ts, code - lib) for both grammars (IDL and
config.idp):
| raw | gzip | xz | |
|---|---|---|---|
probe .wasm |
527 KB | 163 KB | 128 KB |
Valid MVP module (no SIMD / bulk-memory). code 383 KB, data 135 KB
(parser tables + regex DFAs + literals). The tree-sitter-c2rust tables are
compact — the size worry did not materialise.
- Not yet in the number: wasm-bindgen glue + data marshalling (tens of KB),
wasm-opt -Oz (would take ~10–20 % off), and project-aware multi-schema
interpretation. None change the order of magnitude.
std::fs / glob paths (reading config.idp, schema files) compile on wasm32
but error at runtime — resolved by PackageSources (core#42), the
filesystem-free entry that takes the config + schema sources as strings and runs
the same interpretation pass as compile_package.
Open questions
- The
compilation-queue-server— it will be needed for Rust / C / Go handlers (and realcargo buildof alibcrate). Revive it alongside the WASM work, or ship the VM-language loopback first and add it once those land? - Language research — the handler matrix is a hypothesis. Verify each VM's WASM build and payload before committing an order.
- Tutorial content — authored inside the
tutorialapp, or as Markdown indocs/and rendered by both? (TBD.) - Deploy shape — one deploy or three; one domain or subdomains.
- Version sync — the playground pins
core/generationrevs; it inherits the same git-rev treadmill until those crates cut releases.