Skip to content

feat(pages): language guide with run-in-place examples + REPL deep links - #3

Merged
nnunley merged 18 commits into
mainfrom
guide-and-repl
Jul 26, 2026
Merged

feat(pages): language guide with run-in-place examples + REPL deep links#3
nnunley merged 18 commits into
mainfrom
guide-and-repl

Conversation

@mparrett

@mparrett mparrett commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Adds a web rendering of TUTORIAL.md + DEMO.md as docs/fmpl-guide.html, where every code block runs in the browser against the real VM, and wires the browser REPL up to take deep links so any snippet can open in a full session.

The site had "what it is" (the tour) and "try it" (the REPL) but no "learn it" surface. This is that.

What's here

  • docs/fmpl-guide.html — the guide. Each block has ▸ next / » run to end / ↺ reset; blocks carrying -- => or -- Returns: comments assert against them and show a verdict (✓ 3/3 verified). A live terminal in the masthead runs a short scripted session, then hands you the prompt.
  • docs/fmpl-live.js — the run-in-place engine: line normalization for multi-line @{...} and if/else, per-block VM instances, verdicts.
  • fmpl-wasm/src/lib.rs — a ReplVm class alongside the existing global functions. Purely additive; nothing existing changed. Each guide block gets a fresh VM (matching the doctest harness's semantics) while repl.html keeps its long-lived session.
  • docs/repl.html#code=<encoded> prefill, fed through the normal submit path. Safe to auto-run: the wasm build has no network or file builtins.
  • .github/workflows/pages.yml + Cargo.toml — a size-tuned wasm-release profile and a wasm-opt pass. 2.30MB → 1.19MB raw, 553KB → 374KB gzipped.

Two build details worth a look

panic = "abort" in the new profile. VM errors are Results by design, and the one catch_unwind in the tree is in a test, which never builds under this profile, so unwinding machinery is pure weight in the browser. Flagging it because it's the one change here with runtime semantics.

binaryen is pinned to 129. apt's wasm-opt is too old to accept the sign-extension ops rustc emits by default, and Rust also emits bulk-memory ops that wasm-opt has to be told about explicitly. The pin is what I measured against. A floating version will break the deploy the moment the runner image shifts.

Important note

The guide is a hand transcription of TUTORIAL.md / DEMO.md. doc_examples.rs executes the blocks in those files, but nothing reads fmpl-guide.html. A transcription slip wouldn't be caught, and the page can drift from its sources the next time you edit them.

The copy on the page says only what's true: the sources are CI-executed, and the page runs its own blocks live in the browser. It does not claim to be CI-verified itself.

Closing that properly means teaching check_doc_file to pull <pre><code> blocks out of the guide and run them too; the machinery's already there. I left it out to keep this PR to one idea, but I'm happy to do it here or as a follow-up, whichever you prefer.

Notes

  • docs/repl/ stays gitignored; the wasm is built from source on every deploy, never committed.
  • joinMatchBlocks in fmpl-live.js is long (209 lines). It's a line-joining state machine and I think splitting it would hurt more than help, but say the word if you'd rather it were broken up.
  • Verified before opening: all 30 doc blocks pass in-browser (21 assertion-verified, 9 clean), no console errors, no horizontal overflow at 1180px or 430px, cargo check -p fmpl-wasm --target wasm32-unknown-unknown --profile wasm-release passes on this branch.
  • Naming and placement are yours to change — happy to rename the page, move it, or drop the landing-page card if you'd rather the guide live elsewhere.

Companion to #2, which repoints the stale fork URLs on the existing pages.

Background if useful: see mparrett#8 for how this page came together (intended advisory only).


Rendered preview: https://mparrett.github.io/fmpl/fmpl-guide.html. My fork, serving this branch's copy of the page so it can be read rendered rather than as a 1,500-line diff. The live blocks work there. It's a temporary mirror; the fork is coming down once this is settled.

mparrett and others added 17 commits July 25, 2026 08:32
… DEMO.md

Companion page to the engineering tour, same design system. Tutorial is
the spine; DEMO.md's stream/cursor material becomes its own section.
Sketches and network-only examples are labeled; the sources of truth
remain TUTORIAL.md / DEMO.md, which CI executes on every push.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 96e6711)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 722e255)
…uide

repl.html gains a hash runner: #code=<URI-encoded source> is fed through
the normal submit path after wasm init (echoed like typed input, history
populated). Two REPL line-at-a-time gotchas are smoothed at intake:
multi-line @{...} match blocks join to the documented single-line idiom,
and multi-line if/then/else chains join so the else doesn't dangle.

The guide marks browser-safe blocks with pre.run; a small script builds
each link from the block's own text (annotation-only lines dropped), so
no snippet is hand-copied. Network examples, sketches, and the comment
demo are unmarked. Verified: all 29 links execute error-free in the wasm
VM via Playwright, with value assertions on 11.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 75eb405)
[profile.wasm-release]: opt-level=z, fat LTO, 1 CGU, panic=abort, strip.
panic=abort is safe here: VM errors are Results by design (no catch_unwind
in-tree; overflow probed in-browser — clean error, no trap). wasm-opt -Oz
needs --enable-bulk-memory since Rust emits bulk-memory ops by default.

2.1MB/580KB-gzip -> 1.13MB/373KB-gzip, verified against the pinned
wasm-bindgen 0.2.114 locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit a637c37)
The globals expose one persistent session VM; the language guide's
run-in-place blocks need a fresh VM per run (the doctest harness's
semantics) without nuking the session. Exports a constructor plus
eval/is_complete with the same output contract as the globals.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 2415a61)
…-browser

Every runnable block gets ▸ run / edit / reset controls. Run executes
the block in a fresh ReplVm (CI doctest semantics); live results replace
the documented '-- Returns:' lines with ✓/✗ verdicts, and a masthead
'run all blocks' re-verifies the whole page client-side (29/29 locally).
edit swaps the block for a textarea; deep links remain for sharing.

The REPL line-handling logic (multi-line @{} and if/else joining) moves
to a shared docs/fmpl-live.js module used by both repl.html and the
guide, along with the annotation-checking block runner (same conventions
as fmpl-core/tests/doc_examples.rs, including order-insensitive top-level
map compare). wasm loads lazily on first run; prefetch warms the cache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 19b7ea7)
… ops

First CI run of the shrink step failed validation on i32.extend8_s:
ubuntu's packaged binaryen predates default sign-ext support. Pin the
release that matches the locally verified build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit e8d2dbd)
…quiet exits

Assessment findings from a first-visitor walkthrough, addressed:

- The hero looked like a REPL but was frozen — now it IS one: ▸ run
  steps through the demo session statement by statement, and the prompt
  is a real input (same session VM; reset restarts). The affordance no
  longer lies.
- 'run all blocks' was a verification feature posing as the page's first
  CTA, and it spent the one-by-one experience. Split: per-block ▸ run
  now STEPS one statement at a time (≫ finish drains the rest), while
  §13 gains '✓ verify this page' — non-destructive, fresh VM per block,
  per-block verdict chips + badge, no text rewritten. reset-all included.
- Exit-sign demotion: the caption's 'run in your browser' link is gone
  (the page runs in the browser); per-block REPL links shrink to a quiet
  ↗ glyph for sharing.
- Caption diet; 'edit' appears only after a block has been run.

fmpl-live.js: runBlock refactored into createRunner (statement stepper);
runBlock drains it, so verify and stepping share one implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit c70c10d)
Clearing the hint on boot made the first ▸ run visually jarring: the
instruction vanished and the transcript shifted up. The hint is already
written as an FMPL comment, so it simply stays in the scrollback and the
session grows beneath it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 7a4ec17)
…the page

The provenance caption moves above the hero — it sets up the terminal
rather than captioning it after the fact. §1's opening no longer sends
readers to the standalone REPL: the fastest path is this page; the
toolchain block covers local installs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit ba17a49)
…tabs

The full browser REPL gets one subtle mention in §1 (blank-slate scratch
sessions) with a recommendation to keep learning in place. It and the
per-block ↗ links now open in a new tab so the guide is never navigated
away from.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 37f9673)
'…or type FMPL here' kept reappearing after every submitted line. Once
the session is going — stepped or typed — an empty prompt is just an
empty prompt; reset restores the invitation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit fd6c4b4)
…one interaction

The hero terminal and the run-in-place doc blocks were two separate
interaction concepts. Collapse them into one: an amber ▸ in a left gutter
marks the next statement everywhere. The hero is "terminal material"
(stepped lines promote into a growing transcript, cursor becomes the live
prompt); doc blocks are "document material" (running fills the reserved
`-- Returns:` slots in place, line count never changes).

- Fixed control bars (▸ next / » run to end / ↺ reset); buttons disable
  rather than hide so nothing shifts under the pointer between clicks.
- Empty-enter drives the hero script forward — the prompt is the cursor.
- Runner fixes: hasOpenBlockComment() keeps /* */ from splitting across
  statements; isCommentOnly() advances the cursor silently instead of
  hitting the VM (a lone -- is an eval error); findNextStatement() and
  trailingAnnotationSplit() place the cursor and transform annotations
  without the VM. startIdx now threaded through the runner.
- Remove the inline block editor — blocks are read-and-step only.
- Mobile: 16px input row under 40rem stops iOS tap-zoom; auto-focus gated
  behind (pointer: fine) so » run to end doesn't pop the keyboard on touch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit bf3c4e5)
The masthead opened with a possessive attribution — phrasing written when
these pages lived on a fork and had to distinguish whose work was whose.
On the canonical repo that reads as a byline the author didn't ask for.

The dek now describes the tutorial; attribution moves to the meta strip
as a quiet trailing item, and the footer stops repeating it.
The landing page and the tour opened with the same sentence word-for-word,
both leading with a possessive byline, and the guide card repeated it a
third time — a pattern from the fork, where whose-work-was-whose needed
restating everywhere. It reads as branding on the author's own site.

Each dek now leads with the subject and they no longer duplicate. The
guide credits its author in the meta strip; the tour keeps its mentions
in the lineage prose, where they carry information.
'Written by' pushed the strip to two rows, which put the byline alone on
its own line — more prominent than the inline trailing item it replaced.
The CI-verified value loses a redundant verb to make room; the caption
directly below already says CI executes the snippets.
The guide claimed 'CI-verified: every snippet' in four places. CI runs the
blocks in TUTORIAL.md, DEMO.md and README.md — doc_examples.rs never reads
fmpl-guide.html. The page is a hand transcription, so a slip in the HTML
would go uncaught and the page can drift from its sources silently.

What is true: the sources are CI-executed, and this page runs its own
blocks live in the browser against the real VM. The copy now says that
instead. Wiring the guide into doc_examples.rs would make the stronger
claim honest and is worth doing separately.

Also corrects the wasm-release comment: there is a catch_unwind in the
tree, in a test, which never builds under that profile.
The comment carried an estimate (~2.1MB -> ~1.1MB) that had never been
measured against the build CI actually produces. Measuring without running
the bootstrap first compounds it: that links the Rust fallback parser
instead of the FMPL-generated one and understates the artifact by ~30%.

Bootstrapped, matching pages.yml: 2.30MB -> 1.19MB raw, 553KB -> 374KB
gzipped. The result lands within 460 bytes of the deployed artifact, which
the workflow's own 'ls -l' line makes checkable.
@nnunley
nnunley merged commit 4178d65 into main Jul 26, 2026
1 check passed
@nnunley
nnunley deleted the guide-and-repl branch July 26, 2026 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants