The site published at daslang.io and deployed by
.github/workflows/pages.yml. This README is
the canonical guide for running each surface locally — pages.yml references
the same commands and treats this file as the source of truth.
site/
├── index.html # landing — hand-authored
├── downloads.html # downloads & links — hand-authored
├── files/
│ ├── forge.css # tokens + components (source of truth) — includes responsive rules + mobile nav
│ ├── forge.js # sample switcher · install tabs · bench · news (skips CM init on mobile)
│ ├── highlight.js # daslang tokenizer (hero + blog code blocks)
│ ├── runner.js # mini-playground WASM shim
│ ├── cm/ # CodeMirror bundle: codemirror.min.{js,css}, simple-mode.js,
│ │ # daslang-keywords.js, daslang-mode.js, cm-forge.css (Forge theme)
│ ├── lz-string.min.js # share URL compressor (used by /playground/)
│ ├── daslang.svg # master logo (single path, currentColor-tinted)
│ ├── daslang-favicon.svg # yellow-on-black tile favicon
│ ├── daslang.ico # multi-size .ico fallback
│ ├── profile_results.json # fetched by CI from borisbat/dasProfile (gitignored)
│ ├── news.json # generated by build_blog.py (gitignored)
│ └── wasm/ # daslang_static.{js,wasm} from CI (gitignored)
├── blog/
│ ├── _posts/*.md # 25 posts (source of truth — Markdown w/ Hexo extensions)
│ ├── template.html # Forge chrome (used by build_blog.py for posts + index)
│ ├── build_blog.py # generates blog/, news/, changelist.html, news.json
│ ├── index.html # GENERATED — post listing
│ ├── <slug>.html # GENERATED — one per post
│ └── feed.xml # GENERATED — Atom feed
├── _news/*.md # short news entries (one .md per item, body optional)
├── changelist.html # GENERATED by build_blog.py — full news list
├── news/<slug>.html # GENERATED — micro-pages for news entries with bodies
├── playground/
│ ├── index.html # Forge nav + IDE body (mobile gate at top of <body>)
│ ├── forge-skin.css # CSS overrides on web/examples/ui/src/main.css + mobile notice + splitter
│ ├── playground-init.js # URL-hash dispatch (#code= legacy / #z= multi-file)
│ ├── playground-tabs.js # multi-file state, tab strip, autosave to localStorage
│ ├── playground-share.js # ↗ share popover, is.gd shortener
│ ├── playground-splitter.js # draggable code | output column splitter
│ ├── samples/ # multi-file sample bundles (gitignored, mirrored from web/examples/ui/samples)
│ └── *.{js,css} # other vendored bits from web/examples/ui/src/ for local-dev (gitignored)
├── tests/
│ └── playground/ # Playwright e2e suite (28 specs, ~5 s no-WASM)
└── doc/ # Sphinx HTML output (gitignored, deployed by CI)
Sources (forge.css, *.md, *.py, template.html, *.html hand-authored)
are committed. Build artifacts (blog/<slug>.html, news/, changelist.html,
files/wasm/, files/news.json, files/profile_results.json, doc/) are
generated and gitignored — see site/.gitignore.
| Surface | Desktop | Mobile (<768 px) |
|---|---|---|
| Landing hero | CodeMirror editor with sample picker, ▶ run, ↗ playground handoff | Static <pre><code class="language-daslang"> block, no CM init. Hero buttons + sample tabs hidden via CSS. |
| /playground/ | Full multi-file IDE — tabs, splitter, share via #z= hash, ▶ run via WASM |
"Open this on a laptop" notice. pageInit is overridden before WASM fetch — daslang_static.{js,wasm} is never requested. |
| /blog/ | Full post + Disqus comments at the bottom (shortname https-borisbat-github-io-dascf-blog, identifier = slug, Auto theme picks dark via :root { color-scheme: dark }) |
Same. Disqus is responsive. |
| Nav | Inline links: docs · benchmarks · downloads · blog · community + v0.6.3, github ↗, install |
≡ hamburger toggles a dropdown panel with the same links. v0.6.3 chip is hidden at <480 px. |
The mobile fallbacks are pure CSS + a tiny synchronous gate script — no UA
sniffing, no JS feature-detection. Viewport-width is the only signal. Open
Chrome DevTools → device toolbar (Cmd+Shift+M) and reload to test, or browse
from a phone on the same WiFi using your LAN IP (ipconfig getifaddr en0).
cd site && python3 -m http.server 8000
open http://localhost:8000/The landing page works on its own (no build step). Open DevTools — the
benchmarks section, install-tab toggle, and hero sample switcher should all
work without errors. The hero ▶ run button is degraded until you build the
WASM artifact (see below).
pip install --user markdown # one time
python3 site/blog/build_blog.py \
--posts site/blog/_posts \
--news site/_news \
--template site/blog/template.html \
--out site/This regenerates:
site/blog/index.html,site/blog/<slug>.htmlfor all 25 postssite/blog/feed.xml(Atom)site/changelist.html(full news list)site/news/<slug>.htmlfor news entries with bodiessite/files/news.json(top 8 for landing § 05)
Then re-run the http.server. Refresh.
# 1. Create a Markdown file under site/blog/_posts/<slug>.md with front matter:
cat > site/blog/_posts/<slug>.md <<'EOF'
---
title: Your post title
date: 2026-05-13 14:30:00
tags:
- daslang
- performance
---
Body in Markdown. Use ```daslang fenced blocks for code. <!-- more -->
Content after the marker only shows on the post page, not on the index.
Cross-link another post: {% post_link other-slug %}
EOF
# 2. Rebuild (see above)
python3 site/blog/build_blog.py --posts site/blog/_posts --news site/_news \
--template site/blog/template.html --out site/
# 3. Push — pages.yml rebuilds and deploys.# Create a file under site/_news/<slug>.md (no body for a one-liner):
cat > site/_news/<slug>.md <<'EOF'
---
date: 2026-05-13
tag: 0.6.2
title: One-line description
link: https://github.com/.../releases/tag/v0.6.2
---
EOF
# Rebuild — same command as the blog. The entry lands in the top-5 § 05 feed
# on the landing AND in the full /changelist.html. If you add body content
# after the closing ---, build_blog.py also emits /news/<slug>.html.# Local sphinx-build inside the project's venv.
sphinx-build -W --keep-going -b html doc/source build/site
open build/site/index.html(Memory note: on Boris's machine, an alternative path is
/Users/borisbatkin/Library/Python/3.11/bin/sphinx-build, version 7.2.6.)
Verify: dark #0d0c0a background, amber #e8a13a accent on links and the
active sidebar item, JetBrains Mono code blocks, the > typographic logo in
the top-left. Build must complete with -W (zero warnings).
The mini playground needs the WASM artifact at site/files/wasm/. Build it
once (then pip install-free; subsequent ninja rebuilds are fast):
# Install emcc (see web/README.md), then:
source ~/emsdk/emsdk_env.sh
emcmake cmake -S web -B web/cmake_temp -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build web/cmake_temp
# stage_site_playground auto-drops daslang_static.{js,wasm} into site/files/wasm/.Re-run the http.server. The hero ▶ run button should now compile and run
the sample. Edits to the code block re-tokenize on input; click ▶ run to
re-execute.
The full IDE is web/examples/ui/ (CodeMirror, samples picker, multi-file). For local
preview, vendor its source files into site/playground/ and copy the sample
bundles:
# stage_site_playground (from the WASM build above) auto-vendors UI + samples
# + daslang_static.{js,wasm} into site/playground/.
# Forge skin auto-loads from site/playground/forge-skin.css.
# CodeMirror itself + the Forge CM theme load from /files/cm/ (already in site/).Optional: build precompiled .wasm per sample so the playground's "JIT (wasm)"
radio works. all_wasm auto-stages them into site/playground/samples/examples/.
cmake -B build -DDAS_LLVM_DISABLED=OFF -DDAS_BUILD_WASM=ON && cmake --build build --target all_wasmOpen http://localhost:8000/playground/. Forge nav at the top, multi-file
tab strip + Select example + ↗ share + ▶ run on the left of a unified
toolbar, clear on the right. Below: code | output split by a draggable
handle (drag to resize, double-click to reset to 50/50). Layout is one
container — code and output share a single outer border.
- Tabs:
main.dasis the fixed entry point — non-renameable, non-deletable.+addsuntitled1.das,untitled2.das, … Double-click a tab name to rename (validates.dassuffix, rejects duplicates).×deletes (confirms if non-empty). - Run: writes every open file to MEMFS, then
Module.callMain(['main.das']).require utilsinmain.dasresolves against MEMFS as a normal filesystem lookup, so multi-file modules / macros work out of the box (see the "Macros (multi-file)" sample). - Share (↗ share): builds
location + '#z=' + LZString.compressToEncodedURIComponent(JSON.stringify(state)). The popover offers a clipboard copy and an is.gd shorten button. Opening a#z=...URL in a fresh browser restores the full multi-file state. The legacy#code=<percent>hash (emitted by the landing hero's ↗ playground handoff) is still accepted and routes the content intomain.das. - Autosave: 250 ms debounced write of the full state to
localStorage['daslang.playground.state.v1']. Restored on next load only if neither#code=nor#z=is present in the URL. - Splitter position is persisted to
localStorage['daslang.playground.splitLeftPct'].
Replicate pages.yml end-to-end locally:
mkdir -p _site
cp site/*.html _site/
cp site/robots.txt _site/
cp -r site/files _site/files
# Blog
python3 site/blog/build_blog.py \
--posts site/blog/_posts \
--news site/_news \
--template site/blog/template.html \
--out _site/
# Playground (assumes web/ has been built — see above)
mkdir -p _site/playground _site/files/wasm
cp -r web/examples/ui/src/* _site/playground/
cp web/output/daslang_static.{js,wasm} _site/playground/
cp web/output/daslang_static.{js,wasm} _site/files/wasm/
cp site/playground/index.html _site/playground/index.html
cp site/playground/{forge-skin,cm-forge}.css _site/playground/
# Sphinx (requires daslang binary for das2rst — see doc/ build instructions)
sphinx-build -b html doc/source _site/doc
cd _site && python3 -m http.server 8000Click through: landing → blog → a post → docs (Sphinx) → playground → run a sample. No broken links, consistent Forge chrome on every page.
The benchmark numbers come from borisbat/dasProfile/profile_results.json. CI fetches the latest on every publish. Local dev:
curl -sSL -o site/files/profile_results.json \
https://raw.githubusercontent.com/borisbat/dasProfile/main/profile_results.jsonThe benchmarks chart on the landing reads it directly — no rebuild needed.
28 specs covering: dropdowns, tab strip CRUD, multi-file persistence,
share-URL round-trip, splitter drag, hero ↗ playground handoff, mobile gate
(asserts WASM is not fetched at narrow viewports). 5 are tagged @wasm
and only run when the WASM artifact is present.
# Start the dev server from site/ (so paths resolve like prod).
cd site && python3 -m http.server 8765 &
# In another shell:
cd site/tests/playground
npm ci # one-time
npx playwright install chromium # one-time
npx playwright test --grep-invert "@wasm" # ~5 s, no WASM needed
npx playwright test # full suite, requires WASM at site/playground/CI runs the no-WASM subset on every PR via
.github/workflows/playground-e2e.yml.
The @wasm-tagged specs are gated on the WASM build being present locally —
no dedicated CI tier yet.
- Hero
▶ runshows "WASM runtime not deployed" — you haven't built the WASM artifact yet. Either follow "Mini playground" above, or just preview the static landing without it (the run button is the only thing that won't work). /playground/shows a blank page — you forgot tocp web/examples/ui/src/* site/playground/.main.js,main.css, andjquery-3.6.0.min.jsare needed; CodeMirror itself lives atsite/files/cm/and is already in the repo.- Mobile gate doesn't fire — open Chrome DevTools, toggle the device toolbar (Cmd+Shift+M), pick a phone preset, then hard-refresh (Cmd+Shift+R). The mobile detector is a synchronous script that runs once on page load — toggling the toolbar after load doesn't retrigger it.
- Blog code blocks aren't syntax-highlighted — make sure
site/files/highlight.jsis loaded. The blog template includes it; if you built with--out /tmp/foo/and serve from there, your asset paths may diverge from the../files/prefix the template uses. /changelist.htmlis empty — re-runbuild_blog.py; it generates the changelist fromsite/_news/*.md.
The CI flow in .github/workflows/pages.yml
mirrors the steps above. Differences worth knowing:
- CI installs
emsdkfresh every run (cached across runs by GitHub). - CI fetches
profile_results.jsonon each publish. - CI builds Sphinx with
-W(treats warnings as errors). Local builds should do the same before pushing. - CI uploads to GitHub Pages via
actions/deploy-pages@v4.
If something works locally but fails on CI, walk through the workflow file side-by-side with this README — the commands should match.