Operational notes for AI agents working on this repository.
The site is a static index.html plus assets — no build step for the
landing page. The blog is generated by Sheaf, a Wado package rooted in this
repository (wado.toml + src/); see the CMS section below.
Defined in mise.toml. The repo must be trusted once per machine
(mise trust).
| Task | What it does |
|---|---|
mise run fetch-wado |
Shallow-clones wado-lang/wado into .tmp/wado if absent. The blog tasks depend on it for the upstream docs/*.md source (Marl itself now comes from the registry, not the clone). |
mise run fetch |
Re-clones wado-lang/wado into .tmp/wado, copies wado-512.png / wado-1024.png into assets/, and runs scripts/transparentize-logo.py on them. Re-run when the upstream logo changes. Requires Python with Pillow installed. |
mise run blog-build |
Generates the blog and docs: content/*.md → _site/ and the upstream docs/*.md → _site/docs/ via Sheaf (wado run .). |
mise run blog-test |
Runs Sheaf's Wado tests (wado test). |
mise run serve |
Serves the site at http://localhost:8000 via python3 -m http.server. Foreground process — Ctrl-C to stop. |
mise run fetch-playground |
Downloads the wado-playground-web.tar.gz release asset (WADO_PLAYGROUND_VERSION in mise.toml, default latest), verifies its checksum, and unpacks it into playground/runtime/. The deploy/CI path. |
mise run playground-runtime |
Alternative to fetch-playground for local dev against an unreleased wado: stages the runtime from a locally built wado checkout (WADO_DIR, default .tmp/wado; run mise run playground-web-build there first). |
mise run playground-build |
Bundles the playground page JS (playground/src/ + Monaco) into playground/app.js etc. with esbuild (npm ci + node build.mjs). |
mise run playground-test |
E2E smoke test for the built playground page: LSP ready → run → diagnostics → hover in a real Chromium (137+; CHROME_PATH overrides autodetection). |
mise run clean |
Removes .tmp/, _site/, and the playground build outputs. |
.tmp/ and _site/ are gitignored; assets/ is committed (the site must
work without running fetch). The wado CLI itself is a mise tool (a
release binary installed via ubi from wado-lang/wado GitHub Releases).
The blog is generated by Sheaf, a minimal static blog generator written
in Wado and rooted in this repository:
wado.toml at the root, sources under src/. Markdown is rendered by
Marl, the Wado repo's Markdown processor, consumed as the wado-lang:marl
Component Model component from the registry ([dependencies] in wado.toml,
fetched by wado fetch). Marl::render is a pure transform, so it needs no
effect capability (the compiler reconstructs a component's required effects from
its host-leaf imports, and Marl imports none); the templates' own HTML escaping
stays local (src/escape.wado).
The specification, briefly:
-
A pure static transform:
content/*.md→_site/. No server, no database, no persistence between runs; filesystem I/O is the only effect. -
Each post is Markdown opening with fenced JSON front matter:
---json { "title": "A title", "date": "2026-07-11", "tags": [], "index": true } ---titleanddate(ISO 8601) are required;tagsis parsed but unused for now;indexdefaults totrue. -
The slug is the file stem:
content/hello.md→_site/hello.html. -
Outputs: an index page listing posts newest-first, plus one page per post.
-
"index": falsemakes a post unlisted: omitted from the index page and stamped with anoindex, nofollowrobots meta tag, but its page is still generated and reachable by direct URL (a shareable preview). The repo is public, so unlisted is not private. -
Markdown is a CommonMark/GFM subset (Marl). HTML output is safe by construction: raw HTML is escaped rather than passed through, and link destinations are scheme-filtered.
Sheaf also renders the upstream Wado docs. Every .md under the .tmp/wado
clone's docs/ (except AGENTS.md / CLAUDE.md, the agent-facing ones) is
rendered through the same Marl + template + stylesheet as the blog, into
_site/docs/ and published under /docs/.
- No front matter: these are plain Markdown. The page title is the first level-1 heading (the slug if there is none), and there is no author/date byline.
- Relative
.mdlink targets are rewritten to.html, so the whole set cross-links within/docs/. Absolute URLs and other schemes are left alone; links to the two excluded files point at their GitHub source instead. - The docs index (
/docs/) groups pages by slug prefix: a language reference (spec,cheatsheet,design-philosophy), then WEPs (wep-), the standard library (stdlib-), research notes (research-), and everything else under "Other". Marl emits GFM headingids, so in-page#anchorlinks resolve.renderalso returns the heading outline (RenderResult.headings), unused for now — available if a per-page table of contents is wanted.
After generating the site, Sheaf runs an internal link check (linkcheck.wado)
over the output and prints any link whose target is not a generated file. It is
a warning only — the build never fails on it. External URLs, #fragment-only
links, and /assets/* are out of scope. Broken links it reports are typically
upstream doc typos; fix those in the Wado repo.
Deployment: a push to main runs .github/workflows/deploy.yml, which builds
the blog, the docs, and the playground, and publishes the whole site to GitHub
Pages — the landing page at the root, the blog under /blog/, the docs under
/docs/, the playground under /playground/ (see the Playground section).
/playground/ is a standalone page: a Monaco editor backed by wado-lsp
on the left, program stdout/stderr on the right. Compiler, language server,
and the user's program all run client-side as WebAssembly (JSPI required —
Chrome/Chromium 137+). The runtime and its pipeline are documented in
wado-lang/wado:wado-playground/web/README.md.
Committed here: playground/index.html (page shell) and playground/src/
(the esbuild-bundled UI: Monaco wiring, Monarch grammar, LSP↔Monaco
adapter, and share.js — the Share button). Everything coupled to the
compiler — the wasm binary (compiler + bundled LSP engine), the jco bundle, the WASI shims, and the
worker/client JS — is built in the wado repo (wado-playground/web/) and
published as the wado-playground-web.tar.gz release asset, which
mise run fetch-playground unpacks into the gitignored playground/runtime/
(WADO_PLAYGROUND_VERSION in mise.toml selects the release; default
latest).
Share links carry the editor buffer in the URL hash: share.js deflates it
(CompressionStream("deflate-raw")) and base64url-encodes it into #<payload>,
so the code never touches the server. The hash stays in sync with the editor
(debounced, replaceState), so a reload restores the current buffer and the
Share button just copies the already-current link. On load the hash is decoded
back into the editor; an empty or malformed hash silently falls back to the
default source (the untouched default program carries no hash), and shared code
is never auto-run.
Local dev:
mise run fetch-playground # unpack the pinned release runtime into playground/runtime/
mise run playground-build
mise run serve # → http://localhost:8000/playground/To iterate against unreleased wado changes, swap the first step for
WADO_DIR=~/path/to/wado mise run playground-runtime (after
mise run playground-web-build in that checkout).
Deployment: deploy.yml fetches the runtime on every deploy via the
.github/actions/build-playground composite action — mise run fetch-playground (pinned by WADO_PLAYGROUND_VERSION) + mise run playground-build — then copies the result into dist/playground/. No Rust
toolchain or from-source build on the site side. playground-ci.yml runs the
same fetch + build plus playground/test-e2e.mjs against the runner's Chrome
on pull requests and claude/** pushes that touch the playground. Deploys
track the latest wado release by default; set WADO_PLAYGROUND_VERSION to a
tag to freeze or roll back the runtime.
House style for content/*.md:
- Voice — write as "we" and "Wado", the collective project voice. No first-person singular ("I", "my").
- No meta — don't narrate the writing or cross-reference the drafting process ("as the last post said", "our view hasn't changed"). State the point and move on.
- Accurate and concise, but not stiff — plain, direct sentences with a casual edge. Cut filler; keep the personality.
- Verify before asserting — present something as fact only after checking it yourself: run the code, read the source, confirm the number. If it's unverified, leave it out or flag it.
- Never use the word "honest" (or "honestly") — say the thing plainly.
There is no headless browser in PATH, but Playwright's chromium
headless_shell binary is preinstalled at /opt/pw-browsers and the
playwright Node module is available globally at
/opt/node22/lib/node_modules/playwright.
Recipe — start the static server in the background, run a CommonJS script that drives Playwright, then stop the server:
python3 -m http.server 8765 >/tmp/serve.log 2>&1 &
SERVE_PID=$!
sleep 1
cat > /tmp/snap.cjs <<'EOF'
const { chromium } = require('/opt/node22/lib/node_modules/playwright');
(async () => {
const browser = await chromium.launch();
const ctx = await browser.newContext({
viewport: { width: 1100, height: 900 },
deviceScaleFactor: 2,
});
const page = await ctx.newPage();
await page.goto('http://localhost:8765/', { waitUntil: 'networkidle' });
await page.screenshot({ path: '/tmp/wado-hero.png', fullPage: false });
await page.screenshot({ path: '/tmp/wado-full.png', fullPage: true });
await browser.close();
})();
EOF
PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers node /tmp/snap.cjs
kill $SERVE_PIDNotes:
- Use
.cjs, not.mjs— the globalplaywrightpackage only exposes a CommonJS entry point. PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsersis required; without it Playwright looks under~/.cache/ms-playwright/and fails.- For mobile checks, open a second context with
viewport: { width: 414, height: 850 }.
After capture, surface the PNGs to the user with the Read tool — Read
on a PNG path is rendered inline in the chat. Bash output (e.g. ls,
file) is not enough; the user only sees images that come back through
Read.
Read(/tmp/wado-hero.png)
When iterating on visual changes, show only what the user asked for (usually just the desktop hero shot). Don't paste every viewport unless asked.