Wren

Writing documentation

Every page of the documentation site is a Markdown file under docs/. The generator src/tools/docsite turns them into the static site under site/docs/. There is no framework and nothing is fetched at runtime; the pages render in Wren itself.

The site has a second, classic presentation under site/classic/ (plain links, one column of text, light and dark; site/classic/classic.css), reached by "Classic version" in every footer and back by "Modern version". The same tool makes it from the same sources: each doc is rendered a second time in the classic frame, and each hand-written page of the site (site/<page>/index.html) gives its <main> and its scripts, its links moved into the classic tree. Only the classic home, site/classic/index.html, is written by hand; its top, footer, version and release news are stamped between markers. So edit the modern page or the .md, never a generated classic page, and run wrentool docs.

The workflow

  1. Write or edit a .md file under docs/.
  2. If the page is new, add it to docs/_nav.txt so it appears in the sidebar where you want it. A page you forget is still generated and linked (it lands in a "More" section), so nothing is lost, but the order and the section are up to you.
  3. Run wrentool docs. It regenerates site/docs/ and site/classic/ and checks that every internal link and anchor resolves.
  4. Commit the .md and the regenerated site/docs/ and site/classic/. The generation is deterministic (same input, same bytes), and CI fails if either is out of date — the same rule as wrentool changelog -Check.

The sidebar: docs/_nav.txt

The file lists the sections and the pages in each, in the order they appear. Its own header documents the format. In short:

Section Title
  page.md            a page whose label is its first heading
  page.md  Label     a page with an explicit sidebar label
  dir/*    Label      every .md of a directory, as a sub-group (an _index.md or README.md leads it)

The Markdown it understands

The generator implements the slice of CommonMark and GitHub-flavored Markdown the docs use:

  • Headings # to ######. The first # heading is the page title. Each heading gets a stable #anchor.
  • Paragraphs, bold (**x**), italic (*x* or _x_), inline code, and hard breaks (two trailing spaces).
  • Lists, bullet (-, *, +) and ordered (1.), nested by indentation.
  • Links [text](url). A relative link to another doc — remote.md, api/dom.md, ../README.md — is rewritten to the site's URL automatically. A link that leaves docs/ becomes a GitHub link. Keep writing links as .md paths; do not hand-write .html.
  • Images ![alt](src). Put image files under docs/ and reference them relatively.
  • Blockquotes > and thematic breaks (---).
  • Tables, GitHub style, with :---: alignment.
  • Fenced code blocks with a language for syntax highlighting:

    Odin
        main :: proc() { }

    The highlighter knows odin, c, js / javascript, powershell, json, rust and widl. Colouring happens at generation time; there is no client-side highlighter.

A bare <!-- comment --> and a few inline tags (<sub>, <a>, …) pass through. Anything the generator does not understand is shown as escaped text rather than failing, so a mistake is visible on the page.

Generated directives

The conformance page uses two directives the generator fills from data:

  • <figure class="doc-chart"> <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="700" viewBox="0 0 1200 700" role="img" aria-label="Passing WPT subtests of Wren over time" font-family="Segoe UI, Inter, Arial, sans-serif"> <rect width="1200" height="700" fill="#0b0b10"/> <rect x="14" y="14" width="1172" height="672" rx="22" fill="#101018" stroke="#25252f"/> <text x="92" y="66" font-size="34" font-weight="800" fill="#e7e6ee"><tspan fill="#4cc2ff">Wren</tspan> passes 130,423 WPT subtests</text> <text x="92" y="96" font-size="16" fill="#9a99a8">Passing subtests in the folders Wren measures, one point per commit that touches the expectations</text> <line x1="92" x2="970.0" y1="604.0" y2="604.0" stroke="#23232d"/><text x="80" y="609.0" font-size="14" fill="#7d7c8c" text-anchor="end">90k</text> <line x1="92" x2="970.0" y1="507.2" y2="507.2" stroke="#23232d"/><text x="80" y="512.2" font-size="14" fill="#7d7c8c" text-anchor="end">100k</text> <line x1="92" x2="970.0" y1="410.4" y2="410.4" stroke="#23232d"/><text x="80" y="415.4" font-size="14" fill="#7d7c8c" text-anchor="end">110k</text> <line x1="92" x2="970.0" y1="313.6" y2="313.6" stroke="#23232d"/><text x="80" y="318.6" font-size="14" fill="#7d7c8c" text-anchor="end">120k</text> <line x1="92" x2="970.0" y1="216.8" y2="216.8" stroke="#23232d"/><text x="80" y="221.8" font-size="14" fill="#7d7c8c" text-anchor="end">130k</text> <line x1="92" x2="970.0" y1="120.0" y2="120.0" stroke="#23232d"/><text x="80" y="125.0" font-size="14" fill="#7d7c8c" text-anchor="end">140k</text> <line x1="191.8" x2="191.8" y1="120" y2="604.0" stroke="#171720"/><text x="191.8" y="630.0" font-size="13" fill="#7d7c8c" text-anchor="middle">Oct 21</text> <line x1="299.0" x2="299.0" y1="120" y2="604.0" stroke="#171720"/><text x="299.0" y="630.0" font-size="13" fill="#7d7c8c" text-anchor="middle">Oct 22</text> <line x1="406.1" x2="406.1" y1="120" y2="604.0" stroke="#171720"/><text x="406.1" y="630.0" font-size="13" fill="#7d7c8c" text-anchor="middle">Oct 23</text> <line x1="513.2" x2="513.2" y1="120" y2="604.0" stroke="#171720"/><text x="513.2" y="630.0" font-size="13" fill="#7d7c8c" text-anchor="middle">Oct 24</text> <line x1="620.3" x2="620.3" y1="120" y2="604.0" stroke="#171720"/><text x="620.3" y="630.0" font-size="13" fill="#7d7c8c" text-anchor="middle">Oct 25</text> <line x1="727.5" x2="727.5" y1="120" y2="604.0" stroke="#171720"/><text x="727.5" y="630.0" font-size="13" fill="#7d7c8c" text-anchor="middle">Oct 26</text> <line x1="834.6" x2="834.6" y1="120" y2="604.0" stroke="#171720"/><text x="834.6" y="630.0" font-size="13" fill="#7d7c8c" text-anchor="middle">Oct 27</text> <line x1="941.7" x2="941.7" y1="120" y2="604.0" stroke="#171720"/><text x="941.7" y="630.0" font-size="13" fill="#7d7c8c" text-anchor="middle">Oct 28</text> <path d="M92.0,564.7 L101.0,564.7 L101.0,564.8 L112.0,564.8 L112.0,562.7 L140.1,562.7 L140.1,561.2 L148.2,561.2 L148.2,561.3 L241.5,561.3 L241.5,561.3 L247.6,561.3 L247.6,561.3 L255.9,561.3 L255.9,561.3 L260.1,561.3 L260.1,561.3 L262.0,561.3 L262.0,561.3 L262.7,561.3 L262.7,561.3 L276.3,561.3 L276.3,556.7 L293.9,556.7 L293.9,458.3 L344.7,458.3 L344.7,458.3 L424.4,458.3 L424.4,458.3 L450.6,458.3 L450.6,458.3 L539.3,458.3 L539.3,458.3 L583.5,458.3 L583.5,458.3 L631.7,458.3 L631.7,457.9 L724.7,457.9 L724.7,457.9 L786.0,457.9 L786.0,453.2 L791.9,453.2 L791.9,453.2 L796.0,453.2 L796.0,440.1 L796.9,440.1 L796.9,440.1 L812.8,440.1 L812.8,438.3 L815.5,438.3 L815.5,438.3 L821.7,438.3 L821.7,438.3 L821.9,438.3 L821.9,438.3 L832.2,438.3 L832.2,438.3 L843.8,438.3 L843.8,422.1 L854.0,422.1 L854.0,422.1 L855.2,422.1 L855.2,421.0 L856.8,421.0 L856.8,421.0 L857.6,421.0 L857.6,421.0 L858.9,421.0 L858.9,416.8 L861.0,416.8 L861.0,416.8 L931.0,416.8 L931.0,416.8 L964.4,416.8 L964.4,416.8 L970.0,416.8 L970.0,416.8" fill="none" stroke="#4f8cff" stroke-width="3" stroke-linejoin="round"/> <path d="M92.0,564.7 L101.0,564.7 L101.0,564.8 L112.0,564.8 L112.0,562.7 L140.1,562.7 L140.1,447.2 L148.2,447.2 L148.2,447.3 L241.5,447.3 L241.5,446.8 L247.6,446.8 L247.6,444.6 L255.9,444.6 L255.9,446.2 L260.1,446.2 L260.1,444.8 L262.0,444.8 L262.0,443.9 L262.7,443.9 L262.7,441.9 L276.3,441.9 L276.3,442.2 L293.9,442.2 L293.9,336.5 L344.7,336.5 L344.7,334.0 L424.4,334.0 L424.4,334.0 L450.6,334.0 L450.6,319.0 L539.3,319.0 L539.3,304.2 L583.5,304.2 L583.5,304.0 L631.7,304.0 L631.7,303.5 L724.7,303.5 L724.7,303.6 L786.0,303.6 L786.0,298.9 L791.9,298.9 L791.9,298.8 L796.0,298.8 L796.0,285.7 L796.9,285.7 L796.9,285.7 L812.8,285.7 L812.8,283.7 L815.5,283.7 L815.5,283.7 L821.7,283.7 L821.7,279.0 L821.9,279.0 L821.9,279.0 L832.2,279.0 L832.2,278.9 L843.8,278.9 L843.8,258.2 L854.0,258.2 L854.0,256.7 L855.2,256.7 L855.2,255.6 L856.8,255.6 L856.8,255.6 L857.6,255.6 L857.6,255.5 L858.9,255.5 L858.9,251.4 L861.0,251.4 L861.0,251.3 L931.0,251.3 L931.0,227.7 L964.4,227.7 L964.4,218.5 L970.0,218.5 L970.0,212.7" fill="none" stroke="#b784ff" stroke-width="4" stroke-linejoin="round"/> <text x="986.0" y="206.7" font-size="18" font-weight="700" fill="#b784ff">Wren</text><text x="986.0" y="226.7" font-size="14" fill="#c8c7d6">130,423 all folders</text> <text x="986.0" y="420.8" font-size="16" font-weight="700" fill="#4f8cff">Same folders</text><text x="986.0" y="438.8" font-size="14" fill="#c8c7d6">109,335</text> <text x="92" y="652" font-size="13" fill="#6c6b7a">Purple: every folder measured at the time. Blue: only the folders measured at the first point.</text> <text x="92" y="672" font-size="13" fill="#6c6b7a">A sample of WPT (922,978 subtests): not comparable with the wpt.fyi totals of other browsers.</text> </svg> <figcaption>Passing WPT subtests of Wren over time, rebuilt from the git history.</figcaption> </figure> — the WPT pass-rate chart, an inline SVG built from the git history of wpt/expectations.
  • <div class="table-box"><table> <thead><tr><th>Folder</th><th style="text-align:right">Passing</th><th style="text-align:right">Subtests</th><th style="text-align:right">Pass rate</th></tr></thead> <tbody> <tr><td><code>IndexedDB</code></td><td style="text-align:right">3,117</td><td style="text-align:right">3,167</td><td style="text-align:right">98%</td></tr> <tr><td><code>WebCryptoAPI</code></td><td style="text-align:right">52,488</td><td style="text-align:right">70,401</td><td style="text-align:right">75%</td></tr> <tr><td><code>console</code></td><td style="text-align:right">90</td><td style="text-align:right">107</td><td style="text-align:right">84%</td></tr> <tr><td><code>css/css-anchor-position</code></td><td style="text-align:right">12,584</td><td style="text-align:right">13,641</td><td style="text-align:right">92%</td></tr> <tr><td><code>css/css-multicol</code></td><td style="text-align:right">1,112</td><td style="text-align:right">1,616</td><td style="text-align:right">69%</td></tr> <tr><td><code>css/css-nesting</code></td><td style="text-align:right">91</td><td style="text-align:right">126</td><td style="text-align:right">72%</td></tr> <tr><td><code>css/css-shadow</code></td><td style="text-align:right">270</td><td style="text-align:right">377</td><td style="text-align:right">72%</td></tr> <tr><td><code>css/css-transforms</code></td><td style="text-align:right">257</td><td style="text-align:right">685</td><td style="text-align:right">38%</td></tr> <tr><td><code>css/css-ui</code></td><td style="text-align:right">208</td><td style="text-align:right">309</td><td style="text-align:right">67%</td></tr> <tr><td><code>css/css-writing-modes</code></td><td style="text-align:right">266</td><td style="text-align:right">403</td><td style="text-align:right">66%</td></tr> <tr><td><code>css/cssom</code></td><td style="text-align:right">3,843</td><td style="text-align:right">4,403</td><td style="text-align:right">87%</td></tr> <tr><td><code>custom-elements</code></td><td style="text-align:right">2,592</td><td style="text-align:right">3,845</td><td style="text-align:right">67%</td></tr> <tr><td><code>dom/collections</code></td><td style="text-align:right">55</td><td style="text-align:right">60</td><td style="text-align:right">92%</td></tr> <tr><td><code>dom/events</code></td><td style="text-align:right">717</td><td style="text-align:right">924</td><td style="text-align:right">78%</td></tr> <tr><td><code>dom/lists</code></td><td style="text-align:right">187</td><td style="text-align:right">189</td><td style="text-align:right">99%</td></tr> <tr><td><code>dom/nodes</code></td><td style="text-align:right">14,914</td><td style="text-align:right">15,339</td><td style="text-align:right">97%</td></tr> <tr><td><code>domparsing</code></td><td style="text-align:right">446</td><td style="text-align:right">2,175</td><td style="text-align:right">21%</td></tr> <tr><td><code>element-timing</code></td><td style="text-align:right">10</td><td style="text-align:right">55</td><td style="text-align:right">18%</td></tr> <tr><td><code>encoding</code></td><td style="text-align:right">1,151,709</td><td style="text-align:right">1,151,761</td><td style="text-align:right">100%</td></tr> <tr><td><code>fetch/api/basic</code></td><td style="text-align:right">371</td><td style="text-align:right">815</td><td style="text-align:right">46%</td></tr> <tr><td><code>fetch/api/headers</code></td><td style="text-align:right">388</td><td style="text-align:right">501</td><td style="text-align:right">77%</td></tr> <tr><td><code>fetch/api/request</code></td><td style="text-align:right">701</td><td style="text-align:right">973</td><td style="text-align:right">72%</td></tr> <tr><td><code>fetch/api/response</code></td><td style="text-align:right">441</td><td style="text-align:right">521</td><td style="text-align:right">85%</td></tr> <tr><td><code>fetch/cross-origin-resource-policy</code></td><td style="text-align:right">14</td><td style="text-align:right">77</td><td style="text-align:right">18%</td></tr> <tr><td><code>fetch/http-cache</code></td><td style="text-align:right">238</td><td style="text-align:right">280</td><td style="text-align:right">85%</td></tr> <tr><td><code>hr-time</code></td><td style="text-align:right">78</td><td style="text-align:right">88</td><td style="text-align:right">89%</td></tr> <tr><td><code>html/browsers/history</code></td><td style="text-align:right">177</td><td style="text-align:right">229</td><td style="text-align:right">77%</td></tr> <tr><td><code>html/browsers/sandboxing</code></td><td style="text-align:right">21</td><td style="text-align:right">31</td><td style="text-align:right">68%</td></tr> <tr><td><code>html/browsers/windows</code></td><td style="text-align:right">29</td><td style="text-align:right">100</td><td style="text-align:right">29%</td></tr> <tr><td><code>html/cross-origin-embedder-policy</code></td><td style="text-align:right">188</td><td style="text-align:right">386</td><td style="text-align:right">49%</td></tr> <tr><td><code>html/cross-origin-opener-policy</code></td><td style="text-align:right">382</td><td style="text-align:right">442</td><td style="text-align:right">86%</td></tr> <tr><td><code>html/dom</code></td><td style="text-align:right">69,182</td><td style="text-align:right">72,273</td><td style="text-align:right">96%</td></tr> <tr><td><code>html/infrastructure/safe-passing-of-structured-data/shared-array-buffers</code></td><td style="text-align:right">50</td><td style="text-align:right">67</td><td style="text-align:right">75%</td></tr> <tr><td><code>html/semantics/document-metadata/the-link-element</code></td><td style="text-align:right">102</td><td style="text-align:right">147</td><td style="text-align:right">69%</td></tr> <tr><td><code>html/semantics/embedded-content/media-elements</code></td><td style="text-align:right">457</td><td style="text-align:right">741</td><td style="text-align:right">62%</td></tr> <tr><td><code>html/semantics/embedded-content/the-iframe-element</code></td><td style="text-align:right">131</td><td style="text-align:right">229</td><td style="text-align:right">57%</td></tr> <tr><td><code>html/semantics/interactive-elements/the-dialog-element</code></td><td style="text-align:right">376</td><td style="text-align:right">439</td><td style="text-align:right">86%</td></tr> <tr><td><code>html/semantics/scripting-1</code></td><td style="text-align:right">2,393</td><td style="text-align:right">3,649</td><td style="text-align:right">66%</td></tr> <tr><td><code>html/webappapis/microtask-queuing</code></td><td style="text-align:right">14</td><td style="text-align:right">14</td><td style="text-align:right">100%</td></tr> <tr><td><code>html/webappapis/timers</code></td><td style="text-align:right">33</td><td style="text-align:right">35</td><td style="text-align:right">94%</td></tr> <tr><td><code>largest-contentful-paint</code></td><td style="text-align:right">24</td><td style="text-align:right">122</td><td style="text-align:right">20%</td></tr> <tr><td><code>layout-instability</code></td><td style="text-align:right">56</td><td style="text-align:right">118</td><td style="text-align:right">47%</td></tr> <tr><td><code>media-source</code></td><td style="text-align:right">525</td><td style="text-align:right">661</td><td style="text-align:right">79%</td></tr> <tr><td><code>navigation-api</code></td><td style="text-align:right">370</td><td style="text-align:right">575</td><td style="text-align:right">64%</td></tr> <tr><td><code>paint-timing</code></td><td style="text-align:right">17</td><td style="text-align:right">77</td><td style="text-align:right">22%</td></tr> <tr><td><code>performance-timeline</code></td><td style="text-align:right">189</td><td style="text-align:right">226</td><td style="text-align:right">84%</td></tr> <tr><td><code>scheduler</code></td><td style="text-align:right">93</td><td style="text-align:right">155</td><td style="text-align:right">60%</td></tr> <tr><td><code>shadow-dom</code></td><td style="text-align:right">9,386</td><td style="text-align:right">9,858</td><td style="text-align:right">95%</td></tr> <tr><td><code>streams</code></td><td style="text-align:right">1,762</td><td style="text-align:right">2,065</td><td style="text-align:right">85%</td></tr> <tr><td><code>url</code></td><td style="text-align:right">12,294</td><td style="text-align:right">12,323</td><td style="text-align:right">100%</td></tr> <tr><td><code>urlpattern</code></td><td style="text-align:right">1,518</td><td style="text-align:right">1,636</td><td style="text-align:right">93%</td></tr> <tr><td><code>wasm/jsapi</code></td><td style="text-align:right">1,988</td><td style="text-align:right">2,399</td><td style="text-align:right">83%</td></tr> <tr><td><code>wasm/serialization/module</code></td><td style="text-align:right">27</td><td style="text-align:right">38</td><td style="text-align:right">71%</td></tr> <tr><td><code>wasm/webapi</code></td><td style="text-align:right">546</td><td style="text-align:right">586</td><td style="text-align:right">93%</td></tr> <tr><td><code>web-locks</code></td><td style="text-align:right">196</td><td style="text-align:right">269</td><td style="text-align:right">73%</td></tr> <tr><td><code>webaudio</code></td><td style="text-align:right">5,523</td><td style="text-align:right">5,868</td><td style="text-align:right">94%</td></tr> <tr><td><code>webgl</code></td><td style="text-align:right">4,224</td><td style="text-align:right">4,258</td><td style="text-align:right">99%</td></tr> <tr><td><code>webidl/ecmascript-binding</code></td><td style="text-align:right">373</td><td style="text-align:right">471</td><td style="text-align:right">79%</td></tr> <tr><td><code>webmessaging</code></td><td style="text-align:right">180</td><td style="text-align:right">205</td><td style="text-align:right">88%</td></tr> <tr><td><code>webstorage</code></td><td style="text-align:right">1,227</td><td style="text-align:right">1,284</td><td style="text-align:right">96%</td></tr> <tr><td><code>workers</code></td><td style="text-align:right">1,158</td><td style="text-align:right">1,323</td><td style="text-align:right">88%</td></tr> <tr><td><code>xhr</code></td><td style="text-align:right">879</td><td style="text-align:right">2,014</td><td style="text-align:right">44%</td></tr> <tr><td><strong>All measured folders</strong></td><td style="text-align:right"><strong>1,363,277</strong></td><td style="text-align:right"><strong>1,398,151</strong></td><td style="text-align:right"><strong>98%</strong></td></tr> </tbody> </table></div> — the pass rate by folder, read from wpt/expectations.

You rarely need these; they belong to docs/conformance.md.

Rules of thumb

  • One # heading per page, at the top. The site, the search index and the breadcrumbs use it as the title.
  • Link to other docs by their .md path. Never write a site .html URL by hand.
  • Keep headings in order (don't jump from # to ###): the table of contents and screen readers follow the levels.
  • The site has a dark and a light theme (it follows the operating system). Do not assume a background colour in any inline HTML you add.

Edit this page on GitHub