The Deed website and playground, served at https://deed-lang.github.io.
The language itself lives in deed-lang/deed.
This repository owns pages. It does not own the compiler, the wasm artifact, or anything that decides what a Deed program means.
The playground runs a released build of the compiler, pinned to a tag, and asks it. If this site ever shows behaviour that no release has, that is a bug here and not a feature: the fix is to correct the page, or to pin a different tag, never to make the page answer on the compiler's behalf.
Concretely, that rules out a few things that would otherwise be tempting:
- No second formatter.
fmtis one of the exports. - No syntax highlighting written here. The colouring in the playground is the compiler's own lexer, asked on every keystroke, so there is no second grammar to keep in step.
- No error messages written here. Diagnostics are rendered from what the
compiler returns, carets and all, and the index at
errors/is the set of pages the compiler generates rather than a list maintained here. - No examples written here. The playground's are the compiler's corpus at the pinned tag, and the summary under each one is the comment at the top of the file. Which twelve of them the picker offers is a choice made here, and it is the only one: choosing what to show is not writing it, and every file is still served. The landing page's program is the exception, and it is short and its refusal was still produced by running it.
The split exists because the two repositories have different constraints. The compiler's has no dependencies on purpose and its tests are strict in ways that suit a compiler and would suit no website. And a page changes for reasons that have nothing to do with the language, which is history the language should not carry.
Open index.html in a browser, or serve the directory:
$ python3 -m http.server
There is no build step, no package manager and nothing to install. See decisions/2026-07-31-no-build-step.md for why, and for what would change the answer.
Opening play/index.html as a file:// URL will not work, because the wasm
module is loaded with fetch. Serve the directory instead.
assets/deed-<tag>-wasm32-unknown-unknown.wasm is copied from the release of
that tag. It is not built here and it is not edited here. A release asset
cannot be fetched from a browser, so it is committed rather than requested;
the measurement behind that is in the decision record.
Moving the pin:
$ gh release download vX.Y.Z --repo deed-lang/deed --pattern '*.wasm' --dir assets
$ git rm assets/deed-<old tag>-wasm32-unknown-unknown.wasm
$ $EDITOR assets/play.js assets/errors.js # TAG and VERSION
$ $EDITOR install/index.html # the filenames and `deed X.Y.Z`
Two scripts carry the pin and the install page names the same release in
prose, so node tools/check.mjs compares all three and fails if any of them
disagrees. The install page went a day naming a release that was no longer the
latest one, which made its own instructions unfollowable, and nothing noticed.
The page asks the module its version and refuses to run if the answer is not
the one play.js claims, so changing the file without changing the pin, or
the other way round, says so on the page instead of quietly serving the wrong
compiler.
examples/ moves with it, since a program and the compiler that reads it are
not separately pinned:
$ git -C ../deed archive -o /tmp/ex.tar vX.Y.Z examples
$ tar -xf /tmp/ex.tar
$ rm examples/greeting.deed examples/todo.txt
$ node tools/examples.mjs
That last step rewrites examples/index.json, which is what the picker reads.
Everything in it comes from somewhere else: each summary is the comment at the
top of the file, and the rest is what the pinned artifact answered when asked.
What it was asked matters, because "can this be run" has two halves here.
Twenty-one of the twenty-eight have no main at all. Six of the remaining
seven have one and want the filesystem, which a page does not have, so needs
records the capabilities they asked for. That leaves exactly one example this
page can start, and Run is off for the other twenty-seven with the reason
beside it, rather than letting somebody press it and be refused.
The picker shows twelve of the twenty-eight, and that list is in
tools/artifact.mjs because both tools need it. The corpus is not a menu:
about half of it is one language feature at a time, written so the compiler's
own tests have something to read, and a visitor scrolling past sink, names
and diverge is being shown the inside of a test suite. The other sixteen stay
here, still asked about, still reachable by name.
Forgetting that step is caught rather than shipped. tools/check.mjs asks the
same artifact the same questions and fails if the committed answers differ,
which is also what stops a generated file being edited by hand: it looks
exactly like a generated file. It also fails when the picker names a file the
corpus no longer has, so a promoted or deleted example shortens the menu
loudly.
greeting.deed is left out because it imports two other modules and this page
hands the compiler one file. That is the only one: the other twenty-eight were
checked through the pinned artifact and every one of them is clean.
index.html what the language is
play/ the playground
errors/ every diagnostic code, read out of the compiler
install/ how to get a binary running
one-clause/ what a signature turns into
examples/ the compiler's corpus at the pinned tag
assets/ the stylesheet, the scripts, the fonts, the brand files, the compiler
tools/ the check that runs before anything merges, the script
that regenerates the example index, and the code they
both use to ask the compiler
decisions/ why this repository is shaped the way it is
$ node tools/check.mjs
Nothing here fails to compile, so this is what stands between a mistake and the site: every local link resolves, every JSON file parses, every page names the release the pin names, and the example index still says what the compiler says.
That last part is not a shape check. It loads the pinned artifact, asks it its version, and asks it about all twenty-eight examples, because a committed wasm is a file nothing here ever built: a truncated copy, or a different build wearing the right name, spells its filename correctly and passes everything else. It runs on every pull request too.
Apache-2.0, matching the compiler.