Commit 373e48c983

Loris Cro <kappaloris@gmail.com>
2023-08-06 18:12:05
autodoc: new layout (#16715)
* autodoc: init guide TOC work * autodoc: working guides toc navigation * autodoc: more improvements * autodoc: ui refinements * autodoc: new layout and init descriptions for namespaces in std.zig
1 parent 1c7798a
Changed files (4)
lib/docs/commonmark.js
@@ -7831,6 +7831,16 @@
                 } else {
                     node._literal = contents;
                 }
+                const doc = this.options.autoDoc;
+                if (doc) {
+                    const decl_hash = doc.detectDeclPath(contents);
+                    if (decl_hash) {
+                        var l = new Node("link");
+                        l.destination = decl_hash;
+                        l.appendChild(node);
+                        node = l;
+                    }
+                }
                 block.appendChild(node);
                 return true;
             }
@@ -9702,6 +9712,7 @@
 
         this.buffer = "";
         this.lastOut = "\n";
+        this.heading_count = 0;
 
         while ((event = walker.next())) {
             type = event.node.type;
@@ -9883,6 +9894,10 @@
         var tagname = "h" + node.level,
             attrs = this.attrs(node);
         if (entering) {
+            if (node.level != 1) {
+                attrs.push(["id", ":" + this.heading_count]);
+                this.heading_count += 1;
+            }
             this.cr();
             this.tag(tagname, attrs);
         } else {
lib/docs/index.html
@@ -40,15 +40,20 @@
         text-decoration: underline;
       }
 
+      a[href^="src/"] {
+        border-bottom: 2px dotted var(--tx-color);
+      }
+
       .hidden {
-        display: none;
+        display: none !important;
       }
 
       /* layout */
       .canvas {
+        display:flex;
+        flex-direction: column;
         width: 100vw;
         height: 100vh;
-        overflow: hidden;
         margin: 0;
         padding: 0;
         font-family: var(--ui);
@@ -58,13 +63,23 @@
 
       .flex-main {
         display: flex;
-        width: 100%;
-        height: 100%;
+        flex-direction: column;
         justify-content: center;
+        margin: 0 1rem;
+
+        height: 100%;
+        overflow: hidden;
+        
 
         z-index: 100;
       }
 
+      .flex-horizontal {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+      }
+
       .flex-filler {
         flex-grow: 1;
         flex-shrink: 1;
@@ -104,23 +119,27 @@
         z-index: 400;
       }
 
-      /* sidebar */
+      .understated {
+        color: var(--search-other-results-color);
+      }
+
       .sidebar {
-        font-size: 1rem;
         background-color: var(--bg-color);
         box-shadow: 0 0 1rem var(--sidebar-sh-color);
+        clip-path: inset(0px -15px 0px 0px);
       }
 
-      .sidebar .logo {
-        padding: 1rem 0.35rem 0.35rem 0.35rem;
+      .logo {
+        width: 168px;
+        margin-right: 1rem;
       }
 
-      .sidebar .logo > svg {
+      .logo > svg {
         display: block;
         overflow: visible;
       }
 
-      .sidebar ul.guides-api-switch {
+      ul.guides-api-switch {
         display: flex;
         flex-direction: row;
         justify-content: center;
@@ -130,7 +149,7 @@
         padding: 0;
       }
 
-      .sidebar .guides-api-switch a {
+      .guides-api-switch a {
         display: block;
         padding: 0.5rem 1rem;      
         color: var(--sidebar-modlnk-tx-color);
@@ -157,11 +176,24 @@
         background-color: var(--sidebar-modlnk-bg-color-hover);
       }
 
-      .sidebar .guides-api-switch .active {
+      .guides-api-switch .active {
         color: var(--sidebar-modlnk-tx-color-active);
         background-color: var(--sidebar-modlnk-bg-color-active);
       }
 
+      #guidesMenu {
+        height: 100%;
+        overflow: hidden;
+        width: 30%;
+        margin-right: 2rem;
+      }
+      
+      #activeGuide {
+        overflow-y: scroll;
+        height: 100%;
+        width: 70%;
+        padding-right: 1rem;
+      }
       .sidebar h2 {
         margin: 0.5rem;
         padding: 0;
@@ -202,8 +234,16 @@
         font-family: var(--mono);
       }
 
+      #guideTocList {
+        padding: 0 1rem;
+      }
+      
+      #guideTocList ul {
+        padding-left: 1rem;
+        margin: 0;
+      }
+
       #guides {
-        padding: 0rem 0.7rem 2.4rem 1.4rem;
         box-sizing: border-box;
         font-size: 1rem;
         background-color: var(--bg-color);
@@ -212,15 +252,35 @@
 
       /* docs section */
       .docs {
+        flex-grow: 2;
         padding: 0rem 0.7rem 2.4rem 1.4rem;
         font-size: 1rem;
         background-color: var(--bg-color);
         overflow-wrap: break-word;
+        height: 100%;
+        overflow-y: scroll;
+      }
+
+      #noDocsNamespaces {
+      	margin: 1rem;
+      	border: 1px solid var(--search-other-results-color);
+      	padding: 0.5rem 1rem;
+      	background-color: var(--help-bg-color);
+      }
+
+      .column {
+        flex-basis: 0;
+        flex-grow: 1;
+        min-width: 24rem;
+      }
+
+      
+      .search-container {
+        flex-grow: 2;
       }
 
-      .docs .search {
+      .search {
         width: 100%;
-        margin-bottom: 0.8rem;
         padding: 0.5rem;
         font-family: var(--ui);
         font-size: 1rem;
@@ -238,7 +298,7 @@
         -webkit-appearance: none;
       }
 
-      .docs .search:focus {
+      .search:focus {
         background-color: var(--search-bg-color-focus);
         border-bottom-color: #ffbb4d;
         box-shadow: 0 0.3em 1em 0.125em var(--search-sh-color);
@@ -286,7 +346,6 @@
 
       #sectSearchResults {
         box-sizing: border-box;
-        max-width: 960px;
       }
 
       #searchHelp summary {
@@ -335,15 +394,15 @@
       }
       
       
-      .docs a {
+      a {
         color: var(--link-color);
       }
 
-      .docs p {
+      p {
         margin: 0.8rem 0;
       }
 
-      .docs pre {
+      pre {
         font-family: var(--mono);
         font-size: 1em;
         background-color: #F5F5F5;
@@ -351,53 +410,55 @@
         overflow-x: auto;
       }
 
-      .docs pre.inline {
+      pre.inline {
         background-color: var(--bg-color);
         padding: 0;
         display: inline;
       }
 
 
-      .docs code {
+      code {
         font-family: var(--mono);
         font-size: 1em;
       }
 
-      .docs h1 {
+      h1 {
         font-size: 1.4em;
         margin: 0.8em 0;
         padding: 0;
         border-bottom: 0.0625rem dashed;
       }
 
-      .docs h2 {
+      h2 {
         font-size: 1.3em;
         margin: 0.5em 0;
         padding: 0;
         border-bottom: 0.0625rem solid;
       }
-      #listNav {
+      .listNav {
         list-style-type: none;
         margin: 0;
         padding: 0;
         overflow: hidden;
-        background-color: #f1f1f1;
+        boackground-color: #f1f1f1;
+        display: flex;
+        flex-direction: row;
       }
-      #listNav li {
-        float:left;
+      .listNav li {
+      
       }
-      #listNav li a {
+      .listNav li a {
         display: block;
         color: #000;
         text-align: center;
         padding: .5em .8em;
         text-decoration: none;
       }
-      #listNav li a:hover {
+      .listNav li a:hover {
         background-color: #555;
         color: #fff;
       }
-      #listNav li a.active {
+      .listNav li a.active {
         background-color: #FFBB4D;
         color: #000;
       }
@@ -595,7 +656,7 @@
           --tx-color: #bbb;
           --bg-color: #111;
           --link-color: #88f;
-          --sidebar-sh-color: rgba(128, 128, 128, 0.09);
+          --sidebar-sh-color: rgba(128, 128, 128, 0.5);
           --sidebar-mod-bg-color: #333;
           --sidebar-modlnk-tx-color: #fff;
           --sidebar-modlnk-tx-color-hover: #fff;
@@ -612,23 +673,23 @@
         --warning-popover-bg-color: #600000;
         }
 
-        .docs pre {
+        pre {
           background-color:#2A2A2A;
         }
         .fieldDocs {
           border-color:#2A2A2A;
         }
-        #listNav {
+        .listNav {
           background-color: #333;
         }
-        #listNav li a {
+        .listNav li a {
           color: #fff;
         }
-        #listNav li a:hover {
+        .listNav li a:hover {
           background-color: #555;
           color: #fff;
         }
-        #listNav li a.active {
+        .listNav li a.active {
           background-color: #FFBB4D;
           color: #000;
         }
@@ -872,9 +933,8 @@
       <a href="https://github.com/ziglang/zig/wiki/How-to-read-the-standard-library-source-code">Learn more about stdlib source code</a>.
     </div>
     <div id="main" class="flex-main">
-      <div class="flex-filler"></div>
-      <div class="flex-left sidebar">
-        <nav>
+      <div class="flex-horizontal" style="justify-content: center;padding:0.5rem;">
+        <div class="flex-left">
           <div class="logo">
             <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 140">
             <g fill="#F7A41D">
@@ -915,45 +975,35 @@
             </g>
             </svg>
           </div>
-          <div id="sectGudeApiSwitch">
+          <div id="sectGuideApiSwitch">
             <ul class="guides-api-switch">
               <li><a id="ApiSwitch" class="active" href="#A;">API</a></li>
               <li><a id="guideSwitch" class="" href="#G;">Guides</a></li>
             </ul>
           </div>
-          <div id="guidesMenu" class="hidden">
-            <div id="guidesList"></div>
-          </div>
-          <div id="apiMenu" class="hidden">
-            <div id="sectMainMod" class="hidden">
-              <h2><span>Main Module</span></h2>
-              <ul class="modules">
-                <li><a id="mainMod" class="" href=""></a></li>
-              </ul>
-            </div>
-            <div id="sectMods" class="hidden">
-              <h2><span>Dependencies</span></h2>
-              <ul id="listMods" class="modules"></ul>
             </div>
-            <div id="sectInfo" class="hidden">
-              <h2><span>Zig Version</span></h2>
-              <p class="str" id="tdZigVer"></p>
-            </div>
-          </div>
-        </nav>
-      </div>
-      <div class="flex-right">
-        <div class="wrap">
-          <section class="docs" style="padding-top: 1.5rem; padding-bottom:0;">
-          <div style="position: relative">
+        <div class="flex-right" style="padding-top: 0.5rem;overflow:visible;">
+          <div class="search-container" style="position:relative;">
             <span id="searchPlaceholder"><!-- populated by setPrefSlashSearch --></span>
             <input type="search" class="search" id="search" autocomplete="off" spellcheck="false" disabled>
             <div id="dotsPopover">
               Use spaces instead of dots. See $resource for more info.
             </div>
           </div>
-          </section>
+          <div id="sectNavAPI" style="margin-top: 0.5rem;"><ul id="listNavAPI" class="listNav"></ul></div>
+          <div id="sectNavGuides" class="hidden" style="margin-top:0.5rem;background-color:#333;">
+            <ul id="listNavGuides" class="listNav">
+              <li>
+                <a href="#G;" class="active">Index</a>
+              </li>
+              <li style="flex-grow:1;">
+                <a href="#G;" class="active" onclick="scrollGuidesTop(event);"></a>
+              </li>
+            </ul>
+        </div>
         </div>
+      </div>
+      <div style="height:100%; overflow:hidden; margin: 0 1rem;">
         <div id="sectSearchResults" class="docs hidden">
           <details id="searchHelp">
             <summary id="searchHelpSummary" class="normal">How to search effectively</summary>
@@ -986,7 +1036,7 @@
               </ul> 
 
               <h3>Multiple terms</h3>
-              
+            
               <ul>
                 <li>When a search query contains multiple terms, order doesn't matter when 
                     all terms match within a single decl name (e.g. "map auto" will match <code>AutoHashMap</code>).</li>
@@ -1016,13 +1066,17 @@
           </ul>
           <p>Press <kbd>?</kbd> to see keyboard shortcuts and <kbd>Esc</kbd> to return.</p>
         </div>
-        <div id="guides" class="wrap hidden">
+        <div id="guides" class="flex-horizontal hidden" style="align-items:flex-start;height:100%;oveflow:hidden;">
+          <div id="guidesMenu" class="sidebar">
+            <h2 id="guidesMenuTitle">Table of Contents</h2>
+            <div id="guideTocListEmpty" style="margin:0 1rem;"><i>No content to display.</i></div>
+            <div id="guideTocList" style="height:100%;overflow-y:scroll;"></div>
+          </div>
           <div id="activeGuide" class="hidden"></div>
         </div> 
-        <div id="docs" class="wrap hidden">
-          <section class="docs">
+        <div id="docs" class="hidden" style="align-items:flex-start;height:100%;oveflow:hidden;">
+          <section id="docs-scroll" class="docs">
             <p id="status">Loading...</p>
-            <div id="sectNav" class="hidden"><ul id="listNav"></ul></div>
             <div id="fnProto" class="hidden">
               <div class="mobile-scroll-container"><pre id="fnProtoCode" class="scroll-item"></pre></div>
               <div id="fnSourceLink" style="display:flex;flex-direction:row;justify-content:flex-end;"></div>
@@ -1054,13 +1108,25 @@
               <h2>Fields</h2>
               <div id="listFields"></div>
             </div>
-            <div id="sectTypes" class="hidden">
-              <h2>Types</h2>
-              <ul id="listTypes"></ul>
-            </div>
             <div id="sectNamespaces" class="hidden">
-              <h2>Namespaces</h2>
-              <ul id="listNamespaces"></ul>
+              <div style="position:relative;">
+                <h2 style="position:sticky; top:0; background-color:var(--bg-color)">Namespaces</h2>
+                <div class="flex-horizontal" style="justify-content:space-around;align-items:flex-start;flex-wrap:wrap;">
+                  <ul id="listNamespacesLeft" class="column"></ul>
+                  <ul id="listNamespacesRight" class="column"></ul>
+                </div>
+              </div>
+              <h3>Other Namespaces <span style="font-size:1.1rem; cursor:pointer;" title="This box contains namespaces that are exported without a doc comment.">&#9432;</span></h3>
+              <div id="noDocsNamespaces"></div>
+            </div>
+            <div id="sectTypes" class="hidden">
+              <div style="position:relative;">
+                <h2 style="position:sticky; top:0; background-color:var(--bg-color)">Types</h2>
+                <div class="flex-horizontal" style="justify-content:space-around;align-items:flex-start;flex-wrap:wrap;">
+                  <ul id="listTypesLeft" class="column"></ul>
+                  <ul id="listTypesRight" class="column"></ul>
+                </div>
+              </div>
             </div>
             <div id="sectGlobalVars" class="hidden">
               <h2>Global Variables</h2>
lib/docs/main.js
@@ -1,34 +1,42 @@
 "use strict";
 
 var zigAnalysis;
+let skipNextHashChange = null;
 
 const NAV_MODES = {
   API: "#A;",
   GUIDES: "#G;",
 };
 
+
+var scrollHistory = {};
+
 (function() {
   const domBanner = document.getElementById("banner");
   const domMain = document.getElementById("main");
   const domStatus = document.getElementById("status");
-  const domSectNav = document.getElementById("sectNav");
-  const domListNav = document.getElementById("listNav");
+  const domSectNavAPI = document.getElementById("sectNavAPI");
+  const domListNavAPI = document.getElementById("listNavAPI");
+  const domSectNavGuides = document.getElementById("sectNavGuides");
+  const domListNavGuides = document.getElementById("listNavGuides");
   const domApiSwitch = document.getElementById("ApiSwitch");
   const domGuideSwitch = document.getElementById("guideSwitch");
   const domGuidesMenu = document.getElementById("guidesMenu");
-  const domApiMenu = document.getElementById("apiMenu");
-  const domGuidesList = document.getElementById("guidesList");
-  const domSectMainMod = document.getElementById("sectMainMod");
-  const domSectMods = document.getElementById("sectMods");
+  const domGuidesMenuTitle = document.getElementById("guidesMenuTitle");
+  const domGuideTocList = document.getElementById("guideTocList");
+  const domGuideTocListEmtpy = document.getElementById("guideTocListEmpty");
   const domListMods = document.getElementById("listMods");
   const domSectTypes = document.getElementById("sectTypes");
-  const domListTypes = document.getElementById("listTypes");
+  const domListTypesLeft = document.getElementById("listTypesLeft");
+  const domListTypesRight = document.getElementById("listTypesRight");
   const domSectTests = document.getElementById("sectTests");
   const domListTests = document.getElementById("listTests");
   const domSectDocTests = document.getElementById("sectDocTests");
   const domDocTestsCode = document.getElementById("docTestsCode");
   const domSectNamespaces = document.getElementById("sectNamespaces");
-  const domListNamespaces = document.getElementById("listNamespaces");
+  const domListNamespacesLeft = document.getElementById("listNamespacesLeft");
+  const domListNamespacesRight = document.getElementById("listNamespacesRight");
+  const domNoDocsNamespaces = document.getElementById("noDocsNamespaces");
   const domSectErrSets = document.getElementById("sectErrSets");
   const domListErrSets = document.getElementById("listErrSets");
   const domSectFns = document.getElementById("sectFns");
@@ -54,16 +62,17 @@ const NAV_MODES = {
   const domFnNoExamples = document.getElementById("fnNoExamples");
   const domDeclNoRef = document.getElementById("declNoRef");
   const domSearch = document.getElementById("search");
+  const domSearchHelp = document.getElementById("searchHelp");
   const domSearchHelpSummary = document.getElementById("searchHelpSummary");
   const domSectSearchResults = document.getElementById("sectSearchResults");
   const domSectSearchAllResultsLink = document.getElementById("sectSearchAllResultsLink");
   const domDocs = document.getElementById("docs");
+  const domDocsScroll = document.getElementById("docs-scroll");
   const domGuidesSection = document.getElementById("guides");
   const domActiveGuide = document.getElementById("activeGuide");
 
   const domListSearchResults = document.getElementById("listSearchResults");
   const domSectSearchNoResults = document.getElementById("sectSearchNoResults");
-  const domSectInfo = document.getElementById("sectInfo");
   // const domTdTarget = (document.getElementById("tdTarget"));
   const domTdZigVer = document.getElementById("tdZigVer");
   const domHdrName = document.getElementById("hdrName");
@@ -80,6 +89,15 @@ const NAV_MODES = {
 
   domPrefSlashSearch.addEventListener("change", () => setPrefSlashSearch(domPrefSlashSearch.checked));
 
+  const scrollMonitor = [
+    domActiveGuide,
+    domGuideTocList,
+    domDocsScroll,
+    domSectSearchResults,
+  ];
+
+  computeGuideHashes();
+
   let searchTimer = null;
   let searchTrimResults = true;
 
@@ -107,8 +125,10 @@ const NAV_MODES = {
   let canonTypeDecls = null; // lazy; use getCanonTypeDecl
 
   let curNav = {
+    hash: "",
     mode: NAV_MODES.API,
     activeGuide: "",
+    activeGuideScrollTo: null,
     // each element is a module name, e.g. @import("a") then within there @import("b")
     // starting implicitly from root module
     modNames: [],
@@ -423,6 +443,71 @@ const NAV_MODES = {
   //        console.assert(false);
   //        return ({});
   //    }
+    function detectDeclPath(text, context) {
+      let result = "";
+      let separator = ":";
+      const components = text.split(".");
+      let curDeclOrType = undefined;
+      
+      let curContext = context;
+      let limit = 10000;
+      while (curContext) {
+        limit -= 1;
+        
+        if (limit == 0) {
+          throw "too many iterations";
+        }
+        
+        curDeclOrType = findSubDecl(curContext, components[0]);
+        
+        if (!curDeclOrType) {
+          if (curContext.parent_container == null) break;
+          curContext = getType(curContext.parent_container);
+          continue;
+        }
+
+        if (curContext == context) {
+          separator = '.';
+          result = location.hash + separator + components[0];
+        } else {
+          // We had to go up, which means we need a new path!
+          const canonPath = getCanonDeclPath(curDeclOrType.find_subdecl_idx);
+          if (!canonPath) return;
+          
+          let lastModName = canonPath.modNames[canonPath.modNames.length - 1];
+          let fullPath = lastModName + ":" + canonPath.declNames.join(".");
+        
+          separator = '.';
+          result = "#A;" + fullPath;
+        }
+
+        break;
+      } 
+
+      if (!curDeclOrType) {
+        for (let i = 0; i < zigAnalysis.modules.length; i += 1){
+          const p = zigAnalysis.modules[i];
+          if (p.name == components[0]) {
+            curDeclOrType = getType(p.main);
+            result += "#A;" + components[0];
+            break;
+          }
+        }
+      }
+
+      if (!curDeclOrType) return null;
+      
+      for (let i = 1; i < components.length; i += 1) {
+        curDeclOrType = findSubDecl(curDeclOrType, components[i]);
+        if (!curDeclOrType) return null;
+        result += separator + components[i];
+        separator = '.';
+      }
+
+      return result;
+      
+    }
+  
   function renderGuides() {
     renderTitle();
 
@@ -430,46 +515,17 @@ const NAV_MODES = {
     domGuideSwitch.classList.add("active");
     domApiSwitch.classList.remove("active");
     domDocs.classList.add("hidden");
+    domSectNavAPI.classList.add("hidden");
+    domSectNavGuides.classList.remove("hidden");
     domGuidesSection.classList.remove("hidden");
     domActiveGuide.classList.add("hidden");
-    domApiMenu.classList.add("hidden");
     domSectSearchResults.classList.add("hidden");
     domSectSearchAllResultsLink.classList.add("hidden");
     domSectSearchNoResults.classList.add("hidden");
-
-    // sidebar guides list
-    const section_list = zigAnalysis.guide_sections;
-    resizeDomList(domGuidesList, section_list.length, '<div><h2><span></span></h2><ul class="modules"></ul></div>');
-    for (let j = 0; j < section_list.length; j += 1) {
-      const section = section_list[j];
-      const domSectionName = domGuidesList.children[j].children[0].children[0];
-      const domGuides = domGuidesList.children[j].children[1];
-      domSectionName.textContent = section.name;
-      resizeDomList(domGuides, section.guides.length, '<li><a href="#"></a></li>');
-      for (let i = 0; i < section.guides.length; i += 1) {
-        const guide = section.guides[i];
-        let liDom = domGuides.children[i];
-        let aDom = liDom.children[0];
-        aDom.textContent = guide.title;
-        aDom.setAttribute("href", NAV_MODES.GUIDES + guide.name);
-        if (guide.name === curNav.activeGuide) {
-          aDom.classList.add("active");
-        } else {
-          aDom.classList.remove("active");
-        }
-      }
-    }
-
-    if (section_list.length > 0) {
-      domGuidesMenu.classList.remove("hidden");
-    }
-
-
     if (curNavSearch !== "") {
       return renderSearchGuides();
     }
 
-    // main content
     let activeGuide = undefined;
     outer: for (let i = 0; i < zigAnalysis.guide_sections.length; i += 1) {
       const section = zigAnalysis.guide_sections[i];
@@ -482,11 +538,137 @@ const NAV_MODES = {
       }
     }
 
-    if (activeGuide == undefined) {
+
+    // navigation bar 
+    
+    const guideIndexDom = domListNavGuides.children[0].children[0];
+    const guideDom = domListNavGuides.children[1].children[0];
+    if (activeGuide){
+      guideDom.textContent = activeGuide.title;
+      guideDom.setAttribute("href", location.hash);
+      guideDom.classList.remove("hidden");
+      guideIndexDom.classList.remove("active");
+    } else {
+      guideDom.classList.add("hidden");
+      guideIndexDom.classList.add("active");
+    } 
+
+    // main content
+    domGuidesMenuTitle.textContent = "Table of Contents";
+    if (activeGuide) {
+      if (activeGuide.toc != "") {
+        domGuideTocList.innerHTML = activeGuide.toc;
+        // add js callbacks to all links
+        function onLinkClick(ev) {
+          const link = ev.target.getAttribute("href");
+          skipNextHashChange = link;
+          location.replace(link);
+          scrollToHeading(":" + link.split(":")[1], true);
+          ev.preventDefault();
+          ev.stopPropagation();
+        }
+        for (let a of domGuideTocList.querySelectorAll("a")) {
+          a.addEventListener('click', onLinkClick, false); 
+        }
+        domGuideTocList.classList.remove("hidden");
+        domGuideTocListEmtpy.classList.add("hidden");
+      } else {
+        domGuideTocListEmtpy.classList.remove("hidden");
+        domGuideTocList.classList.add("hidden");
+      }
+      
+      let reader = new commonmark.Parser({
+        smart: true,
+        autoDoc: {
+          detectDeclPath: detectDeclPath,
+        }
+      });
+      let ast = reader.parse(activeGuide.body);        
+      let writer = new commonmark.HtmlRenderer();              
+      let result = writer.render(ast);      
+      domActiveGuide.innerHTML = result;
+      if (curNav.activeGuideScrollTo !== null) {
+        scrollToHeading(curNav.activeGuideScrollTo, false);
+      }
+    } else {
+      domGuideTocList.classList.add("hidden");
+      domGuideTocListEmtpy.classList.remove("hidden");
+      
+      if (zigAnalysis.guide_sections.length > 1 || (zigAnalysis.guide_sections[0].guides.length > 0)) {
+        renderGuidesIndex();
+      } else {
+        noGuidesAtAll();
+      }
+    }
+
+    domGuidesMenu.classList.remove("hidden");
+    domActiveGuide.classList.remove("hidden");
+  }
+
+  // TODO: ensure unique hashes
+  // TODO: hash also guides and their headings
+  function computeGuideHashes() {
+      for (let i = 1; i < zigAnalysis.guide_sections.length; i += 1) {
+        const section = zigAnalysis.guide_sections[i];
+        section.hash = "section-" + slugify(section.name || i);
+      }
+  }
+
+  function renderGuidesIndex() {
+    // main content 
+    {
+    let html = "";
+      for (let i = 0; i < zigAnalysis.guide_sections.length; i += 1) {
+        const section = zigAnalysis.guide_sections[i];
+        if (i != 0) { // first section is the default section
+          html += "<h2 id='"+ section.hash +"'>" + section.name + "</h2>";
+      }
+      for (let guide of section.guides) {
+        html += "<ol><li><a href='"+ NAV_MODES.GUIDES + guide.name +"'>" + (guide.title || guide.name) + "</a></li>";
+        html += guide.toc + "</ol>";
+      }
+    }
+    domActiveGuide.innerHTML = html;
+  }
+
+    // sidebar / fast navigation
+    {
+      domGuidesMenuTitle.textContent = "Sections";
+      if (zigAnalysis.guide_sections.length > 1) {
+        let html = "";
+        for (let i = 1; i < zigAnalysis.guide_sections.length; i += 1) {
+          const section = zigAnalysis.guide_sections[i];
+          html += "<li><a href='"+ NAV_MODES.GUIDES + ":" + section.hash +"'>" + section.name + "</a></li>";
+        }
+        domGuideTocList.innerHTML = "<ul>"+html+"</ul>";
+
+        function onLinkClick(ev) {
+          const link = ev.target.getAttribute("href");
+          skipNextHashChange = link;
+          location.replace(link);
+          scrollToHeading(link.split(":")[1], true);
+          ev.preventDefault();
+          ev.stopPropagation();
+        }
+        for (let a of domGuideTocList.querySelectorAll("a")) {
+          a.addEventListener('click', onLinkClick, false); 
+        }
+        
+        domGuideTocList.classList.remove("hidden");
+        domGuideTocListEmtpy.classList.add("hidden");
+      } else {
+        domGuideTocList.classList.add("hidden");
+        domGuideTocListEmtpy.classList.remove("hidden");
+      }
+    }    
+  }
+
+  function noGuidesAtAll() {
       const root_file_idx = zigAnalysis.modules[zigAnalysis.rootMod].file;
       const root_file_name = getFile(root_file_idx).name;
-      domActiveGuide.innerHTML = markdown(`
-# Zig Guides
+    let reader = new commonmark.Parser({smart: true});
+    let ast = reader.parse(`
+# No Guides
 These autodocs don't contain any guide.
 
 While the API section is a reference guide autogenerated from Zig source code,
@@ -520,10 +702,11 @@ You can also create sections to group guides together:
 
 Happy writing!
 `);
-    } else {
-      domActiveGuide.innerHTML = markdown(activeGuide.body);
-    }
-    domActiveGuide.classList.remove("hidden");
+
+    let writer = new commonmark.HtmlRenderer();              
+    let result = writer.render(ast);      
+    domActiveGuide.innerHTML = result;
+
   }
 
   function renderApi() {
@@ -531,29 +714,28 @@ Happy writing!
     domApiSwitch.classList.add("active");
     domGuideSwitch.classList.remove("active");
     domGuidesSection.classList.add("hidden");
+    domSectNavAPI.classList.remove("hidden");
+    domSectNavGuides.classList.add("hidden");
     domDocs.classList.remove("hidden");
-    domApiMenu.classList.remove("hidden");
     domGuidesMenu.classList.add("hidden");
-
     domStatus.classList.add("hidden");
     domFnProto.classList.add("hidden");
     domSectParams.classList.add("hidden");
     domTldDocs.classList.add("hidden");
-    domSectMainMod.classList.add("hidden");
-    domSectMods.classList.add("hidden");
     domSectTypes.classList.add("hidden");
     domSectTests.classList.add("hidden");
     domSectDocTests.classList.add("hidden");
     domSectNamespaces.classList.add("hidden");
+    domListNamespacesLeft.classList.add("hidden");
+    domListNamespacesRight.classList.add("hidden");
+    domNoDocsNamespaces.classList.add("hidden");
     domSectErrSets.classList.add("hidden");
     domSectFns.classList.add("hidden");
     domSectFields.classList.add("hidden");
     domSectSearchResults.classList.add("hidden");
     domSectSearchAllResultsLink.classList.add("hidden");
     domSectSearchNoResults.classList.add("hidden");
-    domSectInfo.classList.add("hidden");
     domHdrName.classList.add("hidden");
-    domSectNav.classList.add("hidden");
     domSectFnErrors.classList.add("hidden");
     domFnExamples.classList.add("hidden");
     domFnNoExamples.classList.add("hidden");
@@ -564,8 +746,6 @@ Happy writing!
     domSectValues.classList.add("hidden");
 
     renderTitle();
-    renderInfo();
-    renderModList();
 
     if (curNavSearch !== "") {
       return renderSearchAPI();
@@ -870,7 +1050,7 @@ Happy writing!
 
   function renderNav() {
     let len = curNav.modNames.length + curNav.declNames.length;
-    resizeDomList(domListNav, len, '<li><a href="#"></a></li>');
+    resizeDomList(domListNavAPI, len, '<li><a href="#"></a></li>');
     let list = [];
     let hrefModNames = [];
     let hrefDeclNames = [];
@@ -891,7 +1071,7 @@ Happy writing!
     }
 
     for (let i = 0; i < list.length; i += 1) {
-      let liDom = domListNav.children[i];
+      let liDom = domListNavAPI.children[i];
       let aDom = liDom.children[0];
       aDom.textContent = list[i].name;
       aDom.setAttribute("href", list[i].link);
@@ -902,67 +1082,60 @@ Happy writing!
       }
     }
 
-    domSectNav.classList.remove("hidden");
   }
 
-  function renderInfo() {
-    domTdZigVer.textContent = zigAnalysis.params.zigVersion;
-    //domTdTarget.textContent = zigAnalysis.params.builds[0].target;
-
-    domSectInfo.classList.remove("hidden");
-  }
 
   function render404() {
     domStatus.textContent = "404 Not Found";
     domStatus.classList.remove("hidden");
   }
 
-  function renderModList() {
-    const rootMod = zigAnalysis.modules[zigAnalysis.rootMod];
-    let list = [];
-    for (let key in rootMod.table) {
-      let modIndex = rootMod.table[key];
-      if (zigAnalysis.modules[modIndex] == null) continue;
-      if (key == rootMod.name) continue;
-      list.push({
-        name: key,
-        mod: modIndex,
-      });
-    }
-
-    {
-      let aDom = domSectMainMod.children[1].children[0].children[0];
-      aDom.textContent = rootMod.name;
-      aDom.setAttribute("href", navLinkMod(zigAnalysis.rootMod));
-      if (rootMod.name === curNav.modNames[0]) {
-        aDom.classList.add("active");
-      } else {
-        aDom.classList.remove("active");
-      }
-      domSectMainMod.classList.remove("hidden");
-    }
-
-    list.sort(function(a, b) {
-      return operatorCompare(a.name.toLowerCase(), b.name.toLowerCase());
-    });
-
-    if (list.length !== 0) {
-      resizeDomList(domListMods, list.length, '<li><a href="#"></a></li>');
-      for (let i = 0; i < list.length; i += 1) {
-        let liDom = domListMods.children[i];
-        let aDom = liDom.children[0];
-        aDom.textContent = list[i].name;
-        aDom.setAttribute("href", navLinkMod(list[i].mod));
-        if (list[i].name === curNav.modNames[0]) {
-          aDom.classList.add("active");
-        } else {
-          aDom.classList.remove("active");
-        }
-      }
+  // function renderModList() {
+  //   const rootMod = zigAnalysis.modules[zigAnalysis.rootMod];
+  //   let list = [];
+  //   for (let key in rootMod.table) {
+  //     let modIndex = rootMod.table[key];
+  //     if (zigAnalysis.modules[modIndex] == null) continue;
+  //     if (key == rootMod.name) continue;
+  //     list.push({
+  //       name: key,
+  //       mod: modIndex,
+  //     });
+  //   }
+
+  //   {
+  //     let aDom = domSectMainMod.children[1].children[0].children[0];
+  //     aDom.textContent = rootMod.name;
+  //     aDom.setAttribute("href", navLinkMod(zigAnalysis.rootMod));
+  //     if (rootMod.name === curNav.modNames[0]) {
+  //       aDom.classList.add("active");
+  //     } else {
+  //       aDom.classList.remove("active");
+  //     }
+  //     domSectMainMod.classList.remove("hidden");
+  //   }
+
+  //   list.sort(function (a, b) {
+  //     return operatorCompare(a.name.toLowerCase(), b.name.toLowerCase());
+  //   });
+
+  //   if (list.length !== 0) {
+  //     resizeDomList(domListMods, list.length, '<li><a href="#"></a></li>');
+  //     for (let i = 0; i < list.length; i += 1) {
+  //       let liDom = domListMods.children[i];
+  //       let aDom = liDom.children[0];
+  //       aDom.textContent = list[i].name;
+  //       aDom.setAttribute("href", navLinkMod(list[i].mod));
+  //       if (list[i].name === curNav.modNames[0]) {
+  //         aDom.classList.add("active");
+  //       } else {
+  //         aDom.classList.remove("active");
+  //       }
+  //     }
 
-      domSectMods.classList.remove("hidden");
-    }
-  }
+  //     domSectMods.classList.remove("hidden");
+  //   }
+  // }
 
   function navLink(modNames, declNames, callName) {
     let base = curNav.mode;
@@ -2525,7 +2698,8 @@ Happy writing!
   function categorizeDecls(
     decls,
     typesList,
-    namespacesList,
+    namespacesWithDocsList,
+    namespacesNoDocsList,
     errSetsList,
     fnsList,
     varsList,
@@ -2562,7 +2736,11 @@ Happy writing!
               if (typeIsErrSet(declValue.expr.type)) {
                 errSetsList.push(decl);
               } else if (typeIsStructWithNoFields(declValue.expr.type)) {
-                namespacesList.push(decl);
+                if (getAstNode(decl.src).docs) {
+                  namespacesWithDocsList.push(decl);
+                } else {
+                  namespacesNoDocsList.push(decl);
+                }
               } else {
                 typesList.push(decl);
               }
@@ -2573,7 +2751,11 @@ Happy writing!
             if (typeIsErrSet(declValue.expr.type)) {
               errSetsList.push(decl);
             } else if (typeIsStructWithNoFields(declValue.expr.type)) {
-              namespacesList.push(decl);
+              if (getAstNode(decl.src).docs) {
+                namespacesWithDocsList.push(decl);
+              } else {
+                namespacesNoDocsList.push(decl);
+              }
             } else {
               typesList.push(decl);
             }
@@ -2608,7 +2790,8 @@ Happy writing!
   function renderContainer(container) {
     let typesList = [];
 
-    let namespacesList = [];
+    let namespacesWithDocsList = [];
+    let namespacesNoDocsList = [];
 
     let errSetsList = [];
 
@@ -2625,7 +2808,8 @@ Happy writing!
     categorizeDecls(
       container.pubDecls,
       typesList,
-      namespacesList,
+      namespacesWithDocsList,
+      namespacesNoDocsList,
       errSetsList,
       fnsList,
       varsList,
@@ -2637,7 +2821,8 @@ Happy writing!
       categorizeDecls(
         container.privDecls,
         typesList,
-        namespacesList,
+        namespacesWithDocsList,
+        namespacesNoDocsList,
         errSetsList,
         fnsList,
         varsList,
@@ -2655,7 +2840,8 @@ Happy writing!
       categorizeDecls(
         uns_container.pubDecls,
         typesList,
-        namespacesList,
+        namespacesWithDocsList,
+        namespacesNoDocsList,
         errSetsList,
         fnsList,
         varsList,
@@ -2667,7 +2853,8 @@ Happy writing!
         categorizeDecls(
           uns_container.privDecls,
           typesList,
-          namespacesList,
+          namespacesWithDocsList,
+          namespacesNoDocsList,
           errSetsList,
           fnsList,
           varsList,
@@ -2678,7 +2865,8 @@ Happy writing!
     }
 
     typesList.sort(byNameProperty);
-    namespacesList.sort(byNameProperty);
+    namespacesWithDocsList.sort(byNameProperty);
+    namespacesNoDocsList.sort(byNameProperty);
     errSetsList.sort(byNameProperty);
     fnsList.sort(byNameProperty);
     varsList.sort(byNameProperty);
@@ -2694,36 +2882,91 @@ Happy writing!
     }
 
     if (typesList.length !== 0) {
-      resizeDomList(
-        domListTypes,
-        typesList.length,
-        '<li><a href=""></a></li>'
-      );
+      const splitPoint = Math.ceil(typesList.length / 2);
+      const template = '<li><a href="#"></a><div></div></li>';
+      resizeDomList(domListTypesLeft, splitPoint, template);
+      resizeDomList(domListTypesRight, typesList.length - splitPoint, template);
+
+      let activeList = domListTypesLeft;
+      let offset = 0;
       for (let i = 0; i < typesList.length; i += 1) {
-        let liDom = domListTypes.children[i];
+        let liDom = activeList.children[i - offset];
         let aDom = liDom.children[0];
         let decl = typesList[i];
         aDom.textContent = decl.name;
         aDom.setAttribute("href", navLinkDecl(decl.name));
+        
+        let descDom = liDom.children[1];
+        let docs = getAstNode(decl.src).docs;
+        if (docs) {
+          descDom.innerHTML = markdown(shortDesc(getAstNode(decl.src).docs));
+        } else {
+          descDom.innerHTML = "<p class='understated'><i>No documentation provided.</i></p>";
+        }
+        if (i == splitPoint - 1) {
+          activeList = domListTypesRight;
+          offset = splitPoint;
+        }
       }
       domSectTypes.classList.remove("hidden");
     }
-    if (namespacesList.length !== 0) {
+    
+    if (namespacesWithDocsList.length !== 0) {
+      const splitPoint = Math.ceil(namespacesWithDocsList.length / 2);
+      const template = '<li><a href="#"></a><div></div></li>';
+      resizeDomList(domListNamespacesLeft, splitPoint, template);
+      resizeDomList(domListNamespacesRight, 
+        namespacesWithDocsList.length - splitPoint, 
+        template);
+
+      let activeList = domListNamespacesLeft;
+      let offset = 0;
+      for (let i = 0; i < namespacesWithDocsList.length; i += 1) {
+        let liDom = activeList.children[i - offset];
+        let aDom = liDom.children[0];
+        let decl = namespacesWithDocsList[i];
+        aDom.textContent = decl.name;
+        aDom.setAttribute("href", navLinkDecl(decl.name));
+        
+        let descDom = liDom.children[1];
+        descDom.innerHTML = markdown(shortDesc(getAstNode(decl.src).docs));
+        if (i == splitPoint - 1) {
+          activeList = domListNamespacesRight;
+          offset = splitPoint;
+      }
+      }
+
+      domListNamespacesLeft.classList.remove("hidden");
+      domListNamespacesRight.classList.remove("hidden");
+      domSectNamespaces.classList.remove("hidden");
+    }
+
+    if (namespacesNoDocsList.length !== 0) {
       resizeDomList(
-        domListNamespaces,
-        namespacesList.length,
-        '<li><a href="#"></a></li>'
+        domNoDocsNamespaces,
+        namespacesNoDocsList.length,
+        '<span><a href="#"></a><span></span></span>'
       );
-      for (let i = 0; i < namespacesList.length; i += 1) {
-        let liDom = domListNamespaces.children[i];
-        let aDom = liDom.children[0];
-        let decl = namespacesList[i];
+      for (let i = 0; i < namespacesNoDocsList.length; i += 1) {
+        let aDom = domNoDocsNamespaces.children[i].children[0];
+        let decl = namespacesNoDocsList[i];
         aDom.textContent = decl.name;
         aDom.setAttribute("href", navLinkDecl(decl.name));
+        let comma = domNoDocsNamespaces.children[i].children[1];
+        if (i == namespacesNoDocsList.length - 1) {
+          comma.textContent = "";
+        } else {
+          comma.textContent = ", ";
+        }
       }
+
+      domNoDocsNamespaces.classList.remove("hidden");
       domSectNamespaces.classList.remove("hidden");
     }
 
+
+    
+
     if (errSetsList.length !== 0) {
       resizeDomList(
         domListErrSets,
@@ -2778,7 +3021,7 @@ Happy writing!
             tdDesc.innerHTML = markdown(short, container);
           }
         } else {
-          tdDesc.innerHTML = "<p><i>No documentation provided.</i><p>";
+          tdDesc.innerHTML = "<p class='understated'><i>No documentation provided.</i><p>";
         }
       }
       domSectFns.classList.remove("hidden");
@@ -3013,12 +3256,15 @@ Happy writing!
 
   function updateCurNav() {
     curNav = {
+      hash: location.hash,
       mode: NAV_MODES.API,
       modNames: [],
       modObjs: [],
       declNames: [],
       declObjs: [],
       callName: null,
+      activeGuide: null,
+      activeGuideScrollTo: null,
     };
     curNavSearch = "";
 
@@ -3039,7 +3285,7 @@ Happy writing!
       case NAV_MODES.API:
         // #A;MODULE:decl.decl.decl?search-term
         curNav.mode = mode;
-
+        {
         let parts = nonSearchPart.split(":");
         if (parts[0] == "") {
           location.hash = DEFAULT_HASH;
@@ -3050,22 +3296,18 @@ Happy writing!
         if (parts[1] != null) {
           curNav.declNames = decodeURIComponent(parts[1]).split(".");
         }
-
+        }
         return;
       case NAV_MODES.GUIDES:
+        curNav.mode = mode;
 
-        const sections = zigAnalysis.guide_sections;
-        if (sections.length != 0 && sections[0].guides.length != 0 && nonSearchPart == "") {
-          location.hash = NAV_MODES.GUIDES + sections[0].guides[0].name;
-          if (qpos != -1) {
-            location.hash += query.substring(qpos);
+        {
+          let parts = nonSearchPart.split(":");
+          curNav.activeGuide = parts[0];
+          if (parts[1] != null) {
+            curNav.activeGuideScrollTo = decodeURIComponent(":" + parts[1]);
           }
-          return;
         }
-
-        curNav.mode = mode;
-        curNav.activeGuide = nonSearchPart;
-
         return;
       default:
         location.hash = DEFAULT_HASH;
@@ -3073,8 +3315,18 @@ Happy writing!
     }
   }
 
-  function onHashChange() {
+  function onHashChange(ev) {
+    scrollHistory[curNav.hash] = scrollMonitor.map(function (x) {
+      return [x, x.scrollTop]
+    });
+    
+    if (skipNextHashChange == decodeURIComponent(location.hash)) {
+      skipNextHashChange = null;
+      return;
+    }
+    skipNextHashChange = null;
     updateCurNav();
+
     if (domSearch.value !== curNavSearch) {
       domSearch.value = curNavSearch;
       if (domSearch.value.length == 0)
@@ -3087,6 +3339,22 @@ Happy writing!
       imFeelingLucky = false;
       activateSelectedResult();
     }
+
+    scroll();
+  }
+
+  function scroll() {
+    const cur = scrollHistory[location.hash];
+    if (cur) {
+      for (let [elem, offset] of cur) {
+        elem.scrollTo(0, offset);
+      }
+    } else {
+      if (curNav.activeGuideScrollTo) return;
+      for (let elem of scrollMonitor) {
+        elem.scrollTo(0, 0);
+      }
+    }
   }
 
   function findSubDecl(parentTypeOrDecl, childName) {
@@ -3384,9 +3652,9 @@ Happy writing!
     let index = trimmed_docs.indexOf("\n\n");
     let cut = false;
 
-    if (index < 0 || index > 80) {
-      if (trimmed_docs.length > 80) {
-        index = 80;
+    if (index < 0 || index > 130) {
+      if (trimmed_docs.length > 130) {
+        index = 130;
         cut = true;
       } else {
         index = trimmed_docs.length;
@@ -3408,31 +3676,118 @@ Happy writing!
       for (let i = 0; i < section.guides.length; i += 1) {
         let reader = new commonmark.Parser({ smart: true });
         const guide = section.guides[i];
-        const ast = reader.parse(guide.body);
 
         // Find the first text thing to use as a sidebar title
-        guide.title = "[empty guide]";
+        guide.title = null;
+        guide.toc = "";
+
+        // Discover Title & TOC for this guide
         {
+          let reader = new commonmark.Parser({smart: true});
+          let ast = reader.parse(guide.body);        
           let walker = ast.walker();
-          let event, node;
+          let heading_idx = 0;
+          let event, node, doc, last, last_ul;
           while ((event = walker.next())) {
             node = event.node;
-            if (node.type === 'text') {
-              guide.title = node.literal;
-              break;
+            if (event.entering) {
+              if (node.type === 'document')  {
+                doc = node;
+                continue;
+              }
+
+              
+              if (node.next) {
+                walker.resumeAt(node.next, true);
+              } else {
+                walker.resumeAt(node, false);
+              }
+              node.unlink();
+              
+              if (node.type === 'heading') {
+                if (node.level == 1) {
+                  if (guide.title == null) {
+                    let doc_node = new commonmark.Node("document", node.sourcepos);
+                    while (node.firstChild) {
+                      doc_node.appendChild(node.firstChild);
+                    }                    
+                    let writer = new commonmark.HtmlRenderer();              
+                    let result = writer.render(doc_node);      
+                    guide.title = result;
+                  }
+                  
+                  continue; // don't index H1
+                }
+
+                // turn heading node into list item & add link node to it
+                {
+                  node._type = "link";
+                  node.destination = NAV_MODES.GUIDES + guide.name + ":" + heading_idx;
+                  heading_idx += 1;
+                  let listItem = new commonmark.Node("item", node.sourcepos);
+                  // TODO: strip links from inside node
+                  listItem.appendChild(node);
+                  listItem.level = node.level;
+                  node = listItem;
+                }
+                
+                if (last_ul) {
+                  // are we inside or outside of it?
+
+                  let target_ul = last_ul;
+                  while(target_ul.level > node.level) {
+                    target_ul = target_ul.parent;
+                  } 
+                  while(target_ul.level < node.level) {
+                    let ul_node = new commonmark.Node("list", node.sourcepos);
+                    ul_node.level = target_ul.level + 1;
+                    ul_node.listType = "bullet";
+                    ul_node.listStart = null;
+                    target_ul.appendChild(ul_node);
+                    target_ul = ul_node;
+                  }
+
+                  target_ul.appendChild(node);
+                  last_ul = target_ul;
+                } else {
+                  let ul_node = new commonmark.Node("list", node.sourcepos);
+                  ul_node.level = 2;
+                  ul_node.listType = "bullet";
+                  ul_node.listStart = null;
+                  doc.prependChild(ul_node);
+            
+                  while (ul_node.level < node.level) {
+                    let current_ul_node = new commonmark.Node("list", node.sourcepos);
+                    current_ul_node.level = ul_node.level + 1;
+                    current_ul_node.listType = "bullet";
+                    current_ul_node.listStart = null;
+                    ul_node.appendChild(current_ul_node);
+                    ul_node = current_ul_node;
+                  }
+
+                  last_ul = ul_node;
+
+                  ul_node.appendChild(node);
             }
           }
         }
+          }        
+          
+          let writer = new commonmark.HtmlRenderer();              
+          let result = writer.render(ast);      
+          guide.toc = result;
+        }
+        
         // Index this guide
         {
-          let walker = ast.walker();
-          let event, node;
-          while ((event = walker.next())) {
-            node = event.node;
-            if (event.entering == true && node.type === 'text') {
-              indexTextForGuide(j, i, node);
-            }
-          }
+          // let walker = guide.ast.walker();
+          // let event, node;
+          // while ((event = walker.next())) {
+          //   node = event.node;
+          //   if (event.entering == true && node.type === 'text') {
+          //       indexTextForGuide(j, i, node);          
+          //   }
+          // }        
         }
       }
     }
@@ -3474,70 +3829,70 @@ Happy writing!
 
 
 
-  function detectDeclPath(text, context) {
-    let result = "";
-    let separator = ":";
-    const components = text.split(".");
-    let curDeclOrType = undefined;
+  // function detectDeclPath(text, context) {
+  //   let result = "";
+  //   let separator = ":";
+  //   const components = text.split(".");
+  //   let curDeclOrType = undefined;
 
-    let curContext = context;
-    let limit = 10000;
-    while (curContext) {
-      limit -= 1;
+  //   let curContext = context;
+  //   let limit = 10000;
+  //   while (curContext) {
+  //     limit -= 1;
 
-      if (limit == 0) {
-        throw "too many iterations";
-      }
-
-      curDeclOrType = findSubDecl(curContext, components[0]);
+  //     if (limit == 0) {
+  //       throw "too many iterations";
+  //     }
 
-      if (!curDeclOrType) {
-        if (curContext.parent_container == null) break;
-        curContext = getType(curContext.parent_container);
-        continue;
-      }
+  //     curDeclOrType = findSubDecl(curContext, components[0]);
 
-      if (curContext == context) {
-        separator = '.';
-        result = location.hash + separator + components[0];
-      } else {
-        // We had to go up, which means we need a new path!
-        const canonPath = getCanonDeclPath(curDeclOrType.find_subdecl_idx);
-        if (!canonPath) return;
+  //     if (!curDeclOrType) {
+  //       if (curContext.parent_container == null) break;
+  //       curContext = getType(curContext.parent_container);
+  //       continue;
+  //     }
 
-        let lastModName = canonPath.modNames[canonPath.modNames.length - 1];
-        let fullPath = lastModName + ":" + canonPath.declNames.join(".");
+  //     if (curContext == context) {
+  //       separator = '.';
+  //       result = location.hash + separator + components[0];
+  //     } else {
+  //       // We had to go up, which means we need a new path!
+  //       const canonPath = getCanonDeclPath(curDeclOrType.find_subdecl_idx);
+  //       if (!canonPath) return;
 
-        separator = '.';
-        result = "#A;" + fullPath;
-      }
+  //       let lastModName = canonPath.modNames[canonPath.modNames.length - 1];
+  //       let fullPath = lastModName + ":" + canonPath.declNames.join(".");
 
-      break;
-    }
+  //       separator = '.';
+  //       result = "#A;" + fullPath;
+  //     }
 
-    if (!curDeclOrType) {
-      for (let i = 0; i < zigAnalysis.modules.length; i += 1) {
-        const p = zigAnalysis.modules[i];
-        if (p.name == components[0]) {
-          curDeclOrType = getType(p.main);
-          result += "#A;" + components[0];
-          break;
-        }
-      }
-    }
+  //     break;
+  //   }
+
+  //   if (!curDeclOrType) {
+  //     for (let i = 0; i < zigAnalysis.modules.length; i += 1) {
+  //       const p = zigAnalysis.modules[i];
+  //       if (p.name == components[0]) {
+  //         curDeclOrType = getType(p.main);
+  //         result += "#A;" + components[0];
+  //         break;
+  //       }
+  //     }
+  //   }
 
-    if (!curDeclOrType) return null;
+  //   if (!curDeclOrType) return null;
 
-    for (let i = 1; i < components.length; i += 1) {
-      curDeclOrType = findSubDecl(curDeclOrType, components[i]);
-      if (!curDeclOrType) return null;
-      result += separator + components[i];
-      separator = '.';
-    }
+  //   for (let i = 1; i < components.length; i += 1) {
+  //     curDeclOrType = findSubDecl(curDeclOrType, components[i]);
+  //     if (!curDeclOrType) return null;
+  //     result += separator + components[i];
+  //     separator = '.';
+  //   }
 
-    return result;
+  //   return result;
 
-  }
+  // }
 
   function activateSelectedResult() {
     if (domSectSearchResults.classList.contains("hidden")) {
@@ -3618,9 +3973,10 @@ Happy writing!
   function onSearchInput(ev) {
     curSearchIndex = -1;
   
-    let replaced = domSearch.value.replaceAll(".", " ");
+    let replaced = domSearch.value.replaceAll(".", " ")
+
+    // Ping red the help text if the user typed a dot.
     if (replaced != domSearch.value) {
-      domSearch.value = replaced;
       domSearchHelpSummary.classList.remove("normal");
       if (domDotsToggleTimeout != null) {
         clearTimeout(domDotsToggleTimeout);
@@ -3629,7 +3985,13 @@ Happy writing!
       domDotsToggleTimeout = setTimeout(function () { 
         domSearchHelpSummary.classList.add("normal"); 
       }, 1000);
+    }
+    
+    replaced = replaced.replace(/  +/g, ' ');
+    if (replaced != domSearch.value) {
+      domSearch.value = replaced;
     } 
+    
     startAsyncSearch();
   }
 
@@ -3832,6 +4194,9 @@ Happy writing!
   }
 
   function renderSearchAPI() {
+    domSectSearchResults.prepend(
+      domSearchHelp.parentElement.removeChild(domSearchHelp)
+    );
     if (canonDeclPaths == null) {
       canonDeclPaths = computeCanonDeclPaths();
     }
@@ -3845,7 +4210,11 @@ Happy writing!
       let term = term_list[i];
       let result = declSearchIndex.search(term.toLowerCase());
       if (result == null) {
+        domSectSearchNoResults.prepend(
+          domSearchHelp.parentElement.removeChild(domSearchHelp)
+        );
         domSectSearchNoResults.classList.remove("hidden");
+        
         domSectSearchResults.classList.add("hidden");
         return;
       }
@@ -3992,6 +4361,27 @@ Happy writing!
     }
   }
 
+  function scrollGuidesTop(ev) {
+      document.getElementById("activeGuide").children[0].scrollIntoView({
+        behavior: "smooth",
+      }); 
+      ev.preventDefault();
+      ev.stopPropagation();
+  }
+  document.scrollGuidesTop = scrollGuidesTop;
+
+  function scrollToHeading(id, alreadyThere) {  
+    // Don't scroll if the current location has a scrolling history.
+    if (scrollHistory[location.hash]) return;
+
+    const c = document.getElementById(id);
+    if (c && alreadyThere) {
+      requestAnimationFrame(() => c.scrollIntoView({behavior: "smooth"}));    
+          } else {
+            requestAnimationFrame(() => c.scrollIntoView());    
+          }
+          return;
+        }
   // function indexNodesToCalls() {
   //     let map = {};
   //     for (let i = 0; i < zigAnalysis.calls.length; i += 1) {
@@ -4473,3 +4863,8 @@ function RadixTree() {
   }
 }
 
+
+function slugify(str) {
+  return str.toLowerCase().trim().replace(/[^\w\s-]/g, '').replace(/[\s_-]+/g, '-').replace(/^-+|-+$/g, '');
+}
+
lib/std/std.zig
@@ -51,52 +51,130 @@ pub const Tz = tz.Tz;
 pub const Uri = @import("Uri.zig");
 
 pub const array_hash_map = @import("array_hash_map.zig");
+
+/// Memory ordering, atomic data structures and operations.
 pub const atomic = @import("atomic.zig");
+
+/// Base64 encoding / decoding.
 pub const base64 = @import("base64.zig");
+
+/// Bit-fiddling data structures.
 pub const bit_set = @import("bit_set.zig");
+
+/// Comptime-available information about the target machine and build mode.
 pub const builtin = @import("builtin.zig");
+
 pub const c = @import("c.zig");
 pub const coff = @import("coff.zig");
+
+/// Compression algorithms such as zlib, zstd, etc.
 pub const compress = @import("compress.zig");
+
+/// Cryptography.
 pub const crypto = @import("crypto.zig");
+
 pub const cstr = @import("cstr.zig");
+
+/// Debug printing, allocation and other debug helpers.
 pub const debug = @import("debug.zig");
 pub const dwarf = @import("dwarf.zig");
 pub const elf = @import("elf.zig");
+
+/// Enum-related metaprogramming helpers.
 pub const enums = @import("enums.zig");
+
+/// Evented I/O data structures.
 pub const event = @import("event.zig");
+
 pub const fifo = @import("fifo.zig");
+
+/// String formatting and parsing (eg parsing numbers out of strings).
 pub const fmt = @import("fmt.zig");
+
+/// File-system related types.
 pub const fs = @import("fs.zig");
+
+/// Fast hashing functions (i.e. not cryptographically secure)
 pub const hash = @import("hash.zig");
 pub const hash_map = @import("hash_map.zig");
+
+/// Allocator implementations.
 pub const heap = @import("heap.zig");
+
+/// HTTP client and server.
 pub const http = @import("http.zig");
+
+/// I/O Streams, Reader/Writer interfaces and common helpers.
 pub const io = @import("io.zig");
+
+/// JSON parsing and serialization.
 pub const json = @import("json.zig");
 pub const leb = @import("leb128.zig");
+
+/// A standardized interface for logging.
 pub const log = @import("log.zig");
 pub const macho = @import("macho.zig");
+
+/// Mathematical constants and operations.
 pub const math = @import("math.zig");
+
+/// Functions for comparing, searching and manipulating memory.
 pub const mem = @import("mem.zig");
+
+/// Metaprogramming helpers.
 pub const meta = @import("meta.zig");
+
+/// Networking.
 pub const net = @import("net.zig");
+
+/// Wrappers around OS-specific APIs.
 pub const os = @import("os.zig");
+
 pub const once = @import("once.zig").once;
+
+/// A set of array and slice types that bit-pack integer elements.
 pub const packed_int_array = @import("packed_int_array.zig");
+
 pub const pdb = @import("pdb.zig");
+
+/// Accessors for process-related info (e.g. command line arguments)
+/// aand spawning of child processes.
 pub const process = @import("process.zig");
+
+/// Fast pseudo-random number generators (i.e. non-cryptographically secure).
 pub const rand = @import("rand.zig");
+
+/// Sorting.
 pub const sort = @import("sort.zig");
+
+/// Single Instruction Multiple Data (SIMD) helpers.
 pub const simd = @import("simd.zig");
+
+/// ASCII text manipulation.
 pub const ascii = @import("ascii.zig");
+
+/// Tar archive format compression / decompression.
 pub const tar = @import("tar.zig");
+
+/// Testing allocator, testing assertions, and other helpers for testing code.
 pub const testing = @import("testing.zig");
+
+/// Sleep, obtaining the current time and constants for conversions.
 pub const time = @import("time.zig");
+
+/// Timezones.
 pub const tz = @import("tz.zig");
+
+/// UTF8 and UTF16LE ecoding / decoding.
 pub const unicode = @import("unicode.zig");
+
+/// Helpers for integrating with Valgrind.
 pub const valgrind = @import("valgrind.zig");
+
+/// Constants and types representing the WASM binary format.
 pub const wasm = @import("wasm.zig");
+
+/// Tokenizing and parsing of Zig code and other Zig-specific language tooling.
 pub const zig = @import("zig.zig");
 pub const start = @import("start.zig");
 
@@ -106,6 +184,7 @@ pub const build = Build;
 const root = @import("root");
 const options_override = if (@hasDecl(root, "std_options")) root.std_options else struct {};
 
+/// Stdlib-wide options that can be overridden by the root file.
 pub const options = struct {
     pub const enable_segfault_handler: bool = if (@hasDecl(options_override, "enable_segfault_handler"))
         options_override.enable_segfault_handler