Skip to content

Latest commit

 

History

History
222 lines (181 loc) · 11 KB

File metadata and controls

222 lines (181 loc) · 11 KB

AGENTS.md

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.

mise tasks

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).

CMS

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 }
    ---

    title and date (ISO 8601) are required; tags is parsed but unused for now; index defaults to true.

  • 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": false makes a post unlisted: omitted from the index page and stamped with a noindex, nofollow robots 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.

Docs

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 .md link 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 heading ids, so in-page #anchor links resolve. render also 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

/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.

Writing posts

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.

Taking screenshots

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_PID

Notes:

  • Use .cjs, not .mjs — the global playwright package only exposes a CommonJS entry point.
  • PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers is required; without it Playwright looks under ~/.cache/ms-playwright/ and fails.
  • For mobile checks, open a second context with viewport: { width: 414, height: 850 }.

Showing screenshots to the user

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.