PageSourceSearch

https://rwarehouse.netlify.app/about

html rwarehouse.netlify.app collected 2026-10-03 11:21:30 UTC 32,387 bytes, 726 lines download raw bytes

1<!DOCTYPE html>
2<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>
3
4<meta charset="utf-8">
5<meta name="generator" content="quarto-1.10.18">
6
7<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
8
9
10<title>About The Warehouse – The Warehouse</title>
11<style>
12/* Default styles provided by pandoc.
13** See https://pandoc.org/MANUAL.html#variables-for-html for config info.
14*/
15span.smallcaps{font-variant: small-caps;}
16div.columns{display: flex; gap: 1.5em;}
17div.column{flex: auto;}
18@media screen {
19div.columns{gap: min(4vw, 1.5em);}
20div.column{overflow-x: auto;}
21}
22div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
23ul.task-list{list-style: none;}
24ul.task-list li input[type="checkbox"] {
25  width: 0.8em;
26  margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */ 
27  vertical-align: middle;
28}
29</style>
30
31
32<script src="site_libs/quarto-nav/quarto-nav.js"></script>
vendor: 1 bytes, line 32
32
33<script src="site_libs/quarto-nav/headroom.min.js"></script>
vendor: 1 bytes, line 33
33
34<script src="site_libs/clipboard/clipboard.min.js"></script>
vendor: 1 bytes, line 34
34
35<script src="site_libs/quarto-search/autocomplete.umd.js"></script>
vendor: 1 bytes, line 35
35
36<script src="site_libs/quarto-search/fuse.min.js"></script>
vendor: 1 bytes, line 36
36
37<script src="site_libs/quarto-search/quarto-search.js"></script>
37
38<meta name="quarto:offset" content="./">
39<script src="site_libs/quarto-html/quarto.js" type="module"></script>
vendor: 1 bytes, line 39
39
40<script src="site_libs/quarto-html/tabsets/tabsets.js" type="module"></script>
vendor: 1 bytes, line 40
40
41<script src="site_libs/quarto-html/popper.min.js"></script>
vendor: 1 bytes, line 41
41
42<script src="site_libs/quarto-html/tippy.umd.min.js"></script>
vendor: 1 bytes, line 42
42
43<script src="site_libs/quarto-html/anchor.min.js"></script>
43
44<link href="site_libs/quarto-html/tippy.css" rel="stylesheet">
45<link href="site_libs/quarto-html/quarto-syntax-highlighting-64a204ab8560d761af7679ceaba22944.css" rel="stylesheet" id="quarto-text-highlighting-styles">
46<script src="site_libs/bootstrap/bootstrap.min.js"></script>
46
47<link href="site_libs/bootstrap/bootstrap-icons.css" rel="stylesheet">
48<link href="site_libs/bootstrap/bootstrap-bfcc276a1845d57ef5c1d60264490f1b.min.css" rel="stylesheet" append-hash="true" id="quarto-bootstrap" data-mode="light">
49<script id="quarto-search-options" type="application/json">{
50  "location": "navbar",
51  "copy-button": false,
52  "collapse-after": 3,
53  "panel-placement": "end",
54  "type": "overlay",
55  "limit": 50,
56  "keyboard-shortcut": [
57    "f",
58    "/",
59    "s"
60  ],
61  "show-item-context": false,
62  "language": {
63    "search-no-results-text": "No results",
64    "search-matching-documents-text": "matching documents",
65    "search-copy-link-title": "Copy link to search",
66    "search-hide-matches-text": "Hide additional matches",
67    "search-more-match-text": "more match in this document",
68    "search-more-matches-text": "more matches in this document",
69    "search-clear-button-title": "Clear",
70    "search-text-placeholder": "",
71    "search-detached-cancel-button-title": "Cancel",
72    "search-submit-button-title": "Submit",
73    "search-label": "Search"
74  }
75}</script>
75
76
77
78<link rel="stylesheet" href="styles.css">
79</head>
80
81<body class="nav-fixed quarto-light">
82
83<div id="quarto-search-results"></div>
84  <header id="quarto-header" class="headroom fixed-top">
85    <nav class="navbar navbar-expand-lg " data-bs-theme="dark">
86      <div class="navbar-container container-fluid">
87      <div class="navbar-brand-container mx-auto">
88    <a class='navbar-brand navbar-brand-logo' href='/'>
89    <img src="./images/the_warehouse_logo.png" alt="" class="navbar-logo light-content">
90    <img src="./images/the_warehouse_logo.png" alt="" class="navbar-logo dark-content">
91    </a>
92    <a class='navbar-brand' href='/'>
93    <span class="navbar-title">The Warehouse</span>
94    </a>
95  </div>
96            <div id="quarto-search" class="" title="Search"></div>
97          <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" role="menu" aria-expanded="false" aria-label="Toggle navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
98  <span class="navbar-toggler-icon"></span>
99</button>
100          <div class="collapse navbar-collapse" id="navbarCollapse">
101            <ul class="navbar-nav navbar-nav-scroll me-auto">
102  <li class="nav-item">
103    <a class='nav-link' href='/'> 
104<span class="menu-text">Home</span></a>
105  </li>  
106  <li class="nav-item">
107    <a class='nav-link' href='/categories/'> 
108<span class="menu-text">Browse</span></a>
109  </li>  
110  <li class="nav-item">
111    <a class='nav-link' href='/reviews'> 
112<span class="menu-text">Reviews</span></a>
113  </li>  
114  <li class="nav-item">
115    <a aria-current='page' class='nav-link active' href='/about'> 
116<span class="menu-text">About</span></a>
117  </li>  
118</ul>
119            <ul class="navbar-nav navbar-nav-scroll ms-auto">
120  <li class="nav-item">
121    <a class='nav-link' href='/submit-review'> <i class="bi bi-star" role="img">
122</i> 
123<span class="menu-text">Write Review</span></a>
124  </li>  
125  <li class="nav-item">
126    <a class='nav-link' href='/submit'> <i class="bi bi-plus-circle" role="img">
127</i> 
128<span class="menu-text">Submit Package</span></a>
129  </li>  
130</ul>
131          </div> <!-- /navcollapse -->
132            <div class="quarto-navbar-tools">
133</div>
134      </div> <!-- /container-fluid -->
135    </nav>
136</header>
137<!-- content -->
138<div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article page-navbar">
139<!-- sidebar -->
140<!-- margin-sidebar -->
141    <div id="quarto-margin-sidebar" class="sidebar margin-sidebar">
142        <nav id="TOC" role="doc-toc" class="toc-active">
143    <h2 id="toc-title">On this page</h2>
144   
145  <ul>
146  <li><a href="#what-is-the-warehouse" id="toc-what-is-the-warehouse" class="nav-link active" data-scroll-target="#what-is-the-warehouse">What is The Warehouse?</a>
147  <ul class="collapse">
148  <li><a href="#the-problem" id="toc-the-problem" class="nav-link" data-scroll-target="#the-problem">The Problem</a></li>
149  <li><a href="#our-solution" id="toc-our-solution" class="nav-link" data-scroll-target="#our-solution">Our Solution</a></li>
150  </ul></li>
151  <li><a href="#how-it-works" id="toc-how-it-works" class="nav-link" data-scroll-target="#how-it-works">How It Works</a></li>
152  <li><a href="#search-strategy" id="toc-search-strategy" class="nav-link" data-scroll-target="#search-strategy">Search Strategy</a></li>
153  <li><a href="#resources-credits" id="toc-resources-credits" class="nav-link" data-scroll-target="#resources-credits">Resources &amp; Credits</a></li>
154  <li><a href="#quality-scores" id="toc-quality-scores" class="nav-link" data-scroll-target="#quality-scores">Quality Scores</a></li>
155  <li><a href="#open-source-free" id="toc-open-source-free" class="nav-link" data-scroll-target="#open-source-free">Open Source &amp; Free</a></li>
156  <li><a href="#get-involved" id="toc-get-involved" class="nav-link" data-scroll-target="#get-involved">Get Involved</a></li>
157  <li><a href="#contact" id="toc-contact" class="nav-link" data-scroll-target="#contact">Contact</a></li>
158  </ul>
159</nav>
160    </div>
161<!-- main -->
162<main class="content" id="quarto-document-content">
163
164<header id="title-block-header" class="quarto-title-block default">
165<div class="quarto-title">
166<h1 class="title">About The Warehouse</h1>
167</div>
168
169
170
171<div class="quarto-title-meta">
172
173    
174  
175    
176  </div>
177  
178
179
180</header>
181
182
183<section id="what-is-the-warehouse" class="level2">
184<h2 class="anchored" data-anchor-id="what-is-the-warehouse">
184What is The Warehouse?</h2>
185<p>The Warehouse is a <strong>function-first R package directory</strong> that helps you find packages by what they do, not just by their names.</p>
186<section id="the-problem" class="level3">
187<h3 class="anchored" data-anchor-id="the-problem">The Problem</h3>
188<p>With over 23,000 packages on CRAN alone, finding the right package for your task is overwhelming:</p>
189<ul>
190<li>Searching by keywords often misses relevant packages</li>
191<li>No easy way to compare similar packages</li>
192<li>Quality indicators are scattered or missing</li>
193<li>GitHub-only packages are hard to discover</li>
194</ul>
195</section>
196<section id="our-solution" class="level3">
197<h3 class="anchored" data-anchor-id="our-solution">Our Solution</h3>
198<p>The Warehouse provides:</p>
199<ul>
200<li><strong>Function-first search</strong>: “estimate serial interval” → find all relevant packages</li>
201<li><strong>Quality scores</strong>: Automated assessment of tests, documentation, and maintenance</li>
202<li><strong>All sources</strong>: CRAN, GitHub, Bioconductor in one place</li>
203<li><strong>Community reviews</strong>: Real user experiences and recommendations</li>
204<li><strong>Smart categorization</strong>: Browse by what packages actually do</li>
205</ul>
206</section>
207</section>
208<section id="how-it-works" class="level2">
209<h2 class="anchored" data-anchor-id="how-it-works">How It Works</h2>
210<div class="quarto-figure quarto-figure-center">
211<figure class="figure">
212<p><img src="images/architecture.png" class="img-fluid figure-img" alt="Architecture diagram showing the data flow from sources through the weekly pipeline to the website and end user"></p>
213<figcaption>The Warehouse — How It Works</figcaption>
214</figure>
215</div>
216<p>The Warehouse is built around a weekly automated pipeline and a static website, with AI used at two points in the process.</p>
217<p><strong>Data pipeline (weekly, automated)</strong> A GitHub Actions workflow runs every week and scrapes package metadata from the <a href="https://r-universe.dev">R-Universe API</a>, which indexes packages from CRAN, Bioconductor, and GitHub. Metadata — name, title, description, topics, quality scores, and author information — is stored in a local SQLite database. Community-submitted packages and user reviews (collected via Google Sheets) are merged in at this stage. Claude then reads each package description and automatically assigns it to one or more categories (e.g.&nbsp;<em>epidemiology</em>, <em>spatial analysis</em>, <em>machine learning</em>). Finally, everything is exported to a flat JSON file that the website reads directly.</p>
218<p><strong>Website</strong> The site is a static <a href="https://quarto.org">Quarto</a> site hosted on Netlify. Because the full package index is exported as a JSON file and loaded in the browser, basic search works instantly without any server round-trip. The site also has a Discover section that surfaces hidden gems (high-quality packages with low download counts) and packages featured in <a href="https://rweekly.org">R Weekly</a>.</p>
219<p><strong>AI</strong> Claude is used in two places: during the pipeline to auto-label packages with categories, and at search time to expand queries and power the chatbot. See the <a href="#search-strategy">Search Strategy</a> section below for details.</p>
220</section>
221<section id="search-strategy" class="level2">
222<h2 class="anchored" data-anchor-id="search-strategy">Search Strategy</h2>
223<div class="quarto-figure quarto-figure-center">
224<figure class="figure">
225<p><img src="images/search-strategy.png" class="img-fluid figure-img" alt="Diagram showing the five-step search flow: query expansion via Claude, BM25 ranking, and result merging, with exact-match shortcut and Fuse.js fallback paths"></p>
226<figcaption>The Warehouse — Search Strategy</figcaption>
227</figure>
228</div>
229<p>Every search is handled by a Netlify serverless function that combines Claude with a BM25 ranking engine.</p>
230<p><strong>1. Query expansion via Claude</strong> The raw query is sent to Claude, which returns two things: a list of R packages likely to match, and a set of related search terms (synonyms, method n
230ames, related concepts). For example, “fit a mixed model” might expand to terms like <em>random effects</em>, <em>lme4</em>, <em>repeated measures</em>, and <em>REML</em>.</p>
231<p><strong>2. BM25 search</strong> The original query and Claude’s expanded terms are run through a BM25 scoring engine over the full package index. BM25 (Best Match 25) is a standard information retrieval algorithm that ranks documents by term frequency — how often a search term appears — while penalising two things that naive term-counting gets wrong: (1) diminishing returns from repeated terms (finding a word 20 times isn’t 20× better than finding it twice), and (2) long documents that mention a term incidentally. Fields are weighted so a match in the package name counts far more than a match in the description.</p>
232<p><strong>3. Merge</strong> Claude’s suggested packages are placed first in the results (verified against the database), followed by the BM25-ranked matches. This means well-known packages Claude directly identifies appear at the top, while the BM25 search catches relevant packages Claude may not have named explicitly.</p>
233<p><strong>Shortcuts and fallbacks</strong> If the query exactly matches a package name, Claude is skipped and the result is returned immediately. If the API call fails or times out, the client falls back to a local <a href="https://fusejs.io">Fuse.js</a> fuzzy search over the same package data.</p>
234</section>
235<section id="resources-credits" class="level2">
236<h2 class="anchored" data-anchor-id="resources-credits">Resources &amp; Credits</h2>
237<p>The Warehouse doesn’t aim to reinvent the wheel. The R ecosystem already has excellent resources for package discovery and distribution. We build upon these foundations, adding a unified search experience focused on <em>what packages do</em> rather than what they’re called.</p>
238<p><strong>Our data sources:</strong></p>
239<table class="caption-top table">
240<thead>
241<tr class="header">
242<th>Resource</th>
243<th>What it provides</th>
244</tr>
245</thead>
246<tbody>
247<tr class="odd">
248<td><a href="https://r-universe.dev">R-Universe</a></td>
249<td>Package metadata, binaries, and quality metrics</td>
250</tr>
251<tr class="even">
252<td><a href="https://cran.r-project.org">CRAN</a></td>
253<td>The official R package repository with 23,000+ packages</td>
254</tr>
255<tr class="odd">
256<td><a href="https://github.com">GitHub</a></td>
257<td>R packages hosted on GitHub, discovered via the GitHub API</td>
258</tr>
259<tr class="even">
260<td><a href="https://bioconductor.org">Bioconductor</a></td>
261<td>Curated packages for bioinformatics and computational biology</td>
262</tr>
263<tr class="odd">
264<td><a href="https://ropensci.org">rOpenSci</a></td>
265<td>Peer-reviewed packages for reproducible research</td>
266</tr>
267<tr class="even">
268<td><a href="https://rweekly.org">R Weekly</a></td>
269<td>Weekly highlights of new and updated R packages</td>
270</tr>
271</tbody>
272</table>
273<p><strong>What The Warehouse adds:</strong></p>
274<ul>
275<li><strong>Unified search</strong> across all sources with AI-powered semantic matching</li>
276<li><strong>Function-first discovery</strong> — search by what you want to do, not package names</li>
277<li><strong>Curated categories</strong> for domain-specific workflows (epidemiology, pharma, geospatial, etc.)</li>
278<li><strong>Community reviews</strong> to share real-world experiences</li>
279<li><strong>Quality comparisons</strong> to help choose between similar packages</li>
280</ul>
281<p>We’re grateful to the maintainers of these resources and the broader R community for making package discovery possible.</p>
282</section>
283<section id="quality-scores" class="level2">
284<h2 class="anchored" data-anchor-id="quality-scores">Quality Scores</h2>
285<p>Quality scores displayed on packages come from <a href="https://r-universe.dev">R-Universe</a>, which calculates them based on factors like documentation, testing, dependencies, and maintenance activity.</p>
286<p><a href="https://docs.r-universe.dev/browse/search.html">Learn more about R-Universe scoring →</a></p>
287</section>
288<section id="open-source-free" class="level2">
289<h2 class="anchored" data-anchor-id="open-source-free">Open Source &amp; Free</h2>
290<p>The Warehouse is:</p>
291<ul>
292<li>✅ Completely free to use</li>
293<li>✅ Open source (MIT license)</li>
294<li>✅ Community-driven</li>
295</ul>
296</section>
297<section id="get-involved" class="level2">
298<h2 class="anchored" data-anchor-id="get-involved">Get Involved</h2>
299<ul>
300<li><strong>Submit packages</strong>: <a href='/submit'>Add your package →</a></li>
301<li><strong>Leave reviews</strong>: <a href='/submit-review'>Share your experience →</a></li>
302<li><strong>
302Contribute</strong>: <a href="https://github.com/kylieainslie/warehouse">GitHub repository →</a></li>
303<li><strong>Report issues</strong>: Found a problem? Let us know!</li>
304</ul>
305</section>
306<section id="contact" class="level2">
307<h2 class="anchored" data-anchor-id="contact">Contact</h2>
308<p>Questions or feedback? Reach out:</p>
309<ul>
310<li>GitHub: <a href="https://github.com/kylieainslie/warehouse">github.com/kylieainslie/warehouse</a></li>
311</ul>
312<hr>
313<p><em>The Warehouse is a prototype project demonstrating function-first package discovery for R and beyond.</em></p>
314<hr>
315<p><small>This website was built with <a href="https://quarto.org">Quarto</a> and created with the help of <a href="https://claude.ai/code">Claude Code</a>.</small></p>
316
317
318</section>
319
320</main> <!-- /main -->
321<script id="quarto-html-after-body" type="application/javascript">
322  window.document.addEventListener("DOMContentLoaded", function (event) {
323    const icon = "";
324    const anchorJS = new window.AnchorJS();
325    anchorJS.options = {
326      placement: 'right',
327      icon: icon
328    };
329    anchorJS.add('.anchored');
330    const isCodeAnnotation = (el) => {
331      for (const clz of el.classList) {
332        if (clz.startsWith('code-annotation-')) {                     
333          return true;
334        }
335      }
336      return false;
337    }
338    const onCopySuccess = function(e) {
339      // button target
340      const button = e.trigger;
341      // don't keep focus
342      button.blur();
343      // flash "checked"
344      button.classList.add('code-copy-button-checked');
345      var currentTitle = button.getAttribute("title");
346      button.setAttribute("title", "Copied!");
347      let tooltip;
348      if (window.bootstrap) {
349        button.setAttribute("data-bs-toggle", "tooltip");
350        button.setAttribute("data-bs-placement", "left");
351        button.setAttribute("data-bs-title", "Copied!");
352        tooltip = new bootstrap.Tooltip(button, 
353          { trigger: "manual", 
354            customClass: "code-copy-button-tooltip",
355            offset: [0, -8]});
356        tooltip.show();    
357      }
358      setTimeout(function() {
359        if (tooltip) {
360          tooltip.hide();
361          button.removeAttribute("data-bs-title");
362          button.removeAttribute("data-bs-toggle");
363          button.removeAttribute("data-bs-placement");
364        }
365        button.setAttribute("title", currentTitle);
366        button.classList.remove('code-copy-button-checked');
367      }, 1000);
368      // clear code selection
369      e.clearSelection();
370    }
371    const getTextToCopy = function(trigger) {
372      const outerScaffold = trigger.parentElement.cloneNode(true);
373      const codeEl = outerScaffold.querySelector('code');
374      for (const childEl of codeEl.children) {
375        if (isCodeAnnotation(childEl)) {
376          childEl.remove();
377        }
378      }
379      return codeEl.innerText;
380    }
381    const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', {
382      text: getTextToCopy
383    });
384    clipboard.on('success', onCopySuccess);
385    if (window.document.getElementById('quarto-embedded-source-code-modal')) {
386      const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', {
387        text: getTextToCopy,
388        container: window.document.getElementById('quarto-embedded-source-code-modal')
389      });
390      clipboardModal.on('success', onCopySuccess);
391    }
392      var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//);
393      var mailtoRegex = new RegExp(/^mailto:/);
394        var filterRegex = new RegExp('/' + window.location.host + '/');
395      var isInternal = (href) => {
396          return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href);
397      }
398      // Inspect non-navigation links and adorn them if external
399     var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)');
400      for (var i=0; i<links.length; i++) {
401        const link = links[i];
402        if (!isInternal(link.href)) {
403          // undo the damage that might have been done by quarto-nav.js in the case of
404          // links that we want to consider external
405          if (link.dataset.originalHref !== undefined) {
406            link.href = link.dataset.originalHref;
407          }
408        }
409      }
410    function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) {
411      const config = {
412        allowHTML: true,
413        maxWidth: 500,
414        delay: 100,
415        arrow: false,
416        appendTo: function(el) {
417            return el.parentElement;
418        },
419        interactive: true,
420        interactiveBorder: 10,
421        theme: 'quarto',
422        placement: 'bottom-start',
423      };
424      if (contentFn) {
425        config.content = contentFn;
426      }
427      if (onTriggerFn) {
428        config.onTrigger = onTriggerFn;
429      }
430      if (onUntriggerFn) {
431        config.onUntrigger = onUntriggerFn;
432      }
433      window.tippy(el, config); 
434    }
435    const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]');
436    for (var i=0; i<noterefs.length; i++) {
437      const ref = noterefs[i];
438      tippyHover(ref, function() {
439        // use id or data attribute instead here
440        let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href');
441        try { href = new URL(href).hash; } catch {}
442        const id = href.replace(/^#\/?/, "");
443        const note = window.document.getElementById(id);
444        if (note) {
445          return note.innerHTML;
446        } else {
447          return "";
448        }
449      });
450    }
451    const xrefs = window.document.querySelectorAll('a.quarto-xref');
452    const processXRef = (id, note) => {
453      // Strip column container classes
454      const stripColumnClz = (el) => {
455        el.classList.remove("page-full", "page-columns");
456        if (el.children) {
457          for (const child of el.children) {
458            stripColumnClz(child);
459          }
460        }
461      }
462      stripColumnClz(note)
463      if (id === null || id.startsWith('sec-')) {
464        // Special case sections, only their first couple elements
465        const container = document.createElement("div");
466        if (note.children && note.children.length > 2) {
467          container.appendChild(note.children[0].cloneNode(true));
468          for (let i = 1; i < note.children.length; i++) {
469            const child = note.children[i];
470            if (child.tagName === "P" && child.innerText === "") {
471              continue;
472            } else {
473              container.appendChild(child.cloneNode(true));
474              break;
475            }
476          }
477          if (window.Quarto?.typesetMath) {
478            window.Quarto.typesetMath(container);
479          }
480          return container.innerHTML
481        } else {
482          if (window.Quarto?.typesetMath) {
483            window.Quarto.typesetMath(note);
484          }
485          return note.innerHTML;
486        }
487      } else {
488        // Remove any anchor links if they are present
489        const anchorLink = note.querySelector('a.anchorjs-link');
490        if (anchorLink) {
491          anchorLink.remove();
492        }
493        if (window.Quarto?.typesetMath) {
494          window.Quarto.typesetMath(note);
495        }
496        if (note.classList.contains("callout")) {
497          return note.outerHTML;
498        } else {
499          return note.innerHTML;
500        }
501      }
502    }
503    for (var i=0; i<xrefs.length; i++) {
504      const xref = xrefs[i];
505      tippyHover(xref, undefined, function(instance) {
506        instance.disable();
507        let url = xref.getAttribute('href');
508        let hash = undefined; 
509        if (url.startsWith('#')) {
510          hash = url;
511        } else {
512          try { hash = new URL(url).hash; } catch {}
513        }
514        if (hash) {
515          const id = hash.replace(/^#\/?/, "");
516          const note = window.document.getElementById(id);
517          if (note !== null) {
518            try {
519              const html = processXRef(id, note.cloneNode(true));
520              instance.setContent(html);
521            } finally {
522              instance.enable();
523              instance.show();
524            }
525          } else {
526            // See if we can fetch this
527            fetch(url.split('#')[0])
528            .then(res => res.text())
529            .then(html => {
530              const parser = new DOMParser();
531              const htmlDoc = parser.parseFromString(html, "text/html");
532              const note = htmlDoc.getElementById(id);
533              if (note !== null) {
534                const html = processXRef(id, note);
535                instance.setContent(html);
536              } 
537            }).finally(() => {
538              instance.enable();
539              instance.show();
540            });
541          }
542        } else {
543          // See if we can fetch a full url (with no hash to target)
544          // This is a special case and we should probably do some content thinning / targeting
545          fetch(url)
546          .then(res => res.text())
547          .then(html => {
548            const parser = new DOMParser();
549            const htmlDoc = parser.parseFromString(html, "text/html");
550            const note = htmlDoc.querySelector('main.content');
551            if (note !== null) {
552              // This should only happen for chapter cross references
553              // (since there is no id in the URL)
554              // remove the first header
555              if (note.children.length > 0 && note.children[0].tagName === "HEADER") {
556                note.children[0].remove();
557              }
558              const html = processXRef(null, note);
559              instance.setContent(html);
560            } 
561          }).finally(() => {
562            instance.enable();
563            instance.show();
564          });
565        }
566      }, function(instance) {
567      });
568    }
569        let selectedAnnoteEl;
570        const selectorForAnnotation = ( cell, annotation) => {
571          let cellAttr = 'data-code-cell="' + cell + '"';
572          let lineAttr = 'data-code-annotation="' +  annotation + '"';
573          const selector = 'span[' + cellAttr + '][' + lineAttr + ']';
574          return selector;
575        }
576        const selectCodeLines = (annoteEl) => {
577          const doc = window.document;
578          const targetCell = annoteEl.getAttribute("data-target-cell");
579          const targetAnnotation = annoteEl.getAttribute("data-target-annotation");
580          const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
581          const lines = annoteSpan.getAttribute("data-code-lines").split(",");
582          const lineIds = lines.map((line) => {
583            return targetCell + "-" + line;
584          })
585          let top = null;
586          let height = null;
587          let parent = null;
588          if (lineIds.length > 0) {
589              //compute the position of the single el (top and bottom and make a div)
590              const el = window.document.getElementById(lineIds[0]);
591              top = el.offsetTop;
592              height = el.offsetHeight;
593              parent = el.parentElement.parentElement;
594            if (lineIds.length > 1) {
595              const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]);
596              const bottom = lastEl.offsetTop + lastEl.offsetHeight;
597              height = bottom - top;
598            }
599            if (top !== null && height !== null && parent !== null) {
600              // cook up a div (if necessary) and position it 
601              let div = window.document.getElementById("code-annotation-line-highlight");
602              if (div === null) {
603                div = window.document.createElement("div");
604                div.setAttribute("id", "code-annotation-line-highlight");
605                div.style.position = 'absolute';
606                parent.appendChild(div);
607              }
608              div.style.top = top - 2 + "px";
609              div.style.height = height + 4 + "px";
610              div.style.left = 0;
611              let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter");
612              if (gutterDiv === null) {
613                gutterDiv = window.document.createElement("div");
614                gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter");
615                gutterDiv.style.position = 'absolute';
616                const codeCell = window.document.getElementById(targetCell);
617                const gutter = codeCell.querySelector('.code-annotation-gutter');
618                gutter.appendChild(gutterDiv);
619              }
620              gutterDiv.style.top = top - 2 + "px";
621              gutterDiv.style.height = height + 4 + "px";
622            }
623            selectedAnnoteEl = annoteEl;
624          }
625        };
626        const unselectCodeLines = () => {
627          const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"];
628          elementsIds.forEach((elId) => {
629            const div = window.document.getElementById(elId);
630            if (div) {
631              div.remove();
632            }
633          });
634          selectedAnnoteEl = undefined;
635        };
636          // Handle positioning of the toggle
637      window.addEventListener(
638        "resize",
639        throttle(() => {
640          elRect = undefined;
641          if (selectedAnnoteEl) {
642            selectCodeLines(selectedAnnoteEl);
643          }
644        }, 10)
645      );
646      function throttle(fn, ms) {
647      let throttle = false;
648      let timer;
649        return (...args) => {
650          if(!throttle) { // first call gets through
651              fn.apply(this, args);
652              throttle = true;
653          } else { // all the others get throttled
654              if(timer) clearTimeout(timer); // cancel #2
655              timer = setTimeout(() => {
656                fn.apply(this, args);
657                timer = throttle = false;
658              }, ms);
659          }
660        };
661      }
662        // Attach click handler to the DT
663        const annoteDls = window.document.querySelectorAll('dt[data-target-cell]');
664        for (const annoteDlNode of annoteDls) {
665          annoteDlNode.addEventListener('click', (event) => {
666            const clickedEl = event.target;
667            if (clickedEl !== selectedAnnoteEl) {
668              unselectCodeLines();
669              const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active');
670              if (activeEl) {
671                activeEl.classList.remove('code-annotation-active');
672              }
673              selectCodeLines(clickedEl);
674              clickedEl.classList.add('code-annotation-active');
675            } else {
676              // Unselect the line
677              unselectCodeLines();
678              clickedEl.classList.remove('code-annotation-active');
679            }
680          });
681        }
682    const findCites = (el) => {
683      const parentEl = el.parentElement;
684      if (parentEl) {
685        const cites = parentEl.dataset.cites;
686        if (cites) {
687          return {
688            el,
689            cites: cites.split(' ')
690          };
691        } else {
692          return findCites(el.parentElement)
693        }
694      } else {
695        return undefined;
696      }
697    };
698    var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]');
699    for (var i=0; i<bibliorefs.length; i++) {
700      const ref = bibliorefs[i];
701      const citeInfo = findCites(ref);
702      if (citeInfo) {
703        tippyHover(citeInfo.el, function() {
704          var popup = window.document.createElement('div');
705          citeInfo.cites.forEach(function(cite) {
706            var citeDiv = window.document.createElement('div');
707            citeDiv.classList.add('hanging-indent');
708            citeDiv.classList.add('csl-entry');
709            var biblioDiv = window.document.getElementById('ref-' + cite);
710            if (biblioDiv) {
711              citeDiv.innerHTML = biblioDiv.innerHTML;
712            }
713            popup.appendChild(citeDiv);
714          });
715          return popup.innerHTML;
716        });
717      }
718    }
719  });
720  </script>
720
721</div> <!-- /content -->
722
723
724
725
726</body></html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.