/* ============================================================ RL-for-LLMs Wiki Reader A static SPA that renders the knowledge base — topic articles and source records — with full LaTeX, tables, code, frontmatter, and live [source:] citation links. ============================================================ */ 'use strict'; const API = 'https://rl-llm-wiki-rl-bucket-sync.hf.space'; const DATASET = 'rl-llm-wiki/knowledge-base'; const RESOLVE = `https://huggingface.co/datasets/${DATASET}/resolve/main`; const DATASET_BLOB = `https://huggingface.co/datasets/${DATASET}/blob/main`; const BUCKET = `https://huggingface.co/buckets/rl-llm-wiki/rl-main-bucket`; const state = { pages: [], // [{path,title,parent,maturity}] sources: [], // [{id,title,summary_path,summary_url,bucket_path,original_url}] srcById: new Map(), // id -> source meta prs: [], queueCount: null, leaderboard: [], ready: false, cache: new Map(), // url -> text taxonomy: null, // parsed taxonomy.yaml { cat: {description, nodes:{slug:scope}} } tax: [], // ordered [{cat, description, nodes:[{slug,scope,page}], written, total}] collapsed: new Set(JSON.parse(localStorage.getItem('viz-collapsed') || '[]')), treeCollapsed: new Set(JSON.parse(localStorage.getItem('viz-tree-collapsed') || '[]')), srcNs: null, // selected source-namespace filter (null = all) pageContent: new Map(), // path -> article markdown (cached) searchText: new Map(), // topic path -> lowercased body, for nav content search citedBy: new Map(), // sourceId -> Set(topic path) that cite it citeCount: new Map(), // sourceId -> # distinct articles citing it wordCount: new Map(), // topic path -> prose word count refCount: new Map(), // topic path -> # inline [source:] citations citemapStarted: false, citemapReady: false, sourcesLoaded: false, // ids known (tree/cache/api) → citations resolve sourceTitlesLoaded: false, // titles known (cache/api, not the tree stage) srcSort: 'cites', // 'cites' | 'year' | 'az' srcFilter: null, // null | 'cited' | 'uncited' — coverage facet on the sources browser srcMeta: new Map(), // id -> {title, authors, year, venue} for hovercards (lazy) firstRenderDone: false, // gate background softRefresh until the first paint lands contributors: null, // # agents who've merged/reviewed (from the leaderboard) updates: [], // recent merged PRs (latest-updates feed) }; /* ── small DOM helpers ─────────────────────────────────── */ const $ = (s, r = document) => r.querySelector(s); const $$ = (s, r = document) => Array.from(r.querySelectorAll(s)); const el = (t, c, h) => { const e = document.createElement(t); if (c) e.className = c; if (h != null) e.innerHTML = h; return e; }; const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, m => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[m])); /* ── share links / toast ───────────────────────────────── */ function copyLink(url) { const done = () => toast('Link copied to clipboard'); if (navigator.clipboard?.writeText) navigator.clipboard.writeText(url).then(done, () => fallbackCopy(url, done)); else fallbackCopy(url, done); } function fallbackCopy(text, cb) { const ta = el('textarea'); ta.value = text; ta.style.cssText = 'position:fixed;opacity:0'; document.body.append(ta); ta.select(); try { document.execCommand('copy'); cb(); } catch (e) {} ta.remove(); } let _toastT; function toast(msg) { let t = $('#toast'); if (!t) { t = el('div'); t.id = 'toast'; document.body.append(t); } t.textContent = msg; t.classList.add('show'); clearTimeout(_toastT); _toastT = setTimeout(() => t.classList.remove('show'), 1800); } const pageShareUrl = () => location.origin + location.pathname + location.hash.split('~')[0]; /* ── source hovercard (title · authors · year) ─────────── */ // Lazily fetches a source's summary frontmatter on hover (cached). Bounded: only // the sources you actually hover are fetched. async function fetchSourceMeta(id) { if (state.srcMeta.has(id)) return state.srcMeta.get(id); const meta = state.srcById.get(id); let info = { id, title: meta?.title || id, year: sourceYear(id) }; try { const path = meta?.summary_path || `sources/${sanitizeId(id)}.md`; const { fm } = parseFrontmatter(await getText(`${RESOLVE}/${path}`)); info = { id, title: fm.title || meta?.title || id, authors: Array.isArray(fm.authors) ? fm.authors.join(', ') : fm.authors, year: fm.year || sourceYear(id), venue: fm.venue, type: fm.type, }; } catch (e) { /* keep the cheap info */ } state.srcMeta.set(id, info); return info; } // Ensure the given sources have titles, reading each one's frontmatter title from // the CDN (fast, cached) — a reliable fallback for the slow/flaky /v1/sources so // titles show for what's actually on screen. Bounded: only fetches missing ones, // once each, then re-renders. No-op if titles already loaded (cache/API/tree). async function ensureTitles(list) { const need = (list || []).filter(s => s && !s.title && !s._titleTried); if (!need.length) return; need.forEach(s => { s._titleTried = true; }); await Promise.all(need.map(async s => { try { const m = await fetchSourceMeta(s.id); if (m && m.title && m.title !== s.id) s.title = m.title; } catch (e) {} })); if (need.some(s => s.title)) { state.sourceTitlesLoaded = true; softRefresh(); } } let _hoverId = null, _hoverTimer = null; function hovercardEl() { let hc = $('#hovercard'); if (!hc) { hc = el('div'); hc.id = 'hovercard'; document.body.append(hc); } return hc; } function showHovercard(anchor, id) { fetchSourceMeta(id).then(info => { if (_hoverId !== id) return; // pointer moved away while fetching const authors = info.authors ? (info.authors.length > 90 ? info.authors.slice(0, 88) + '…' : info.authors) : ''; const line = [info.year, info.venue].filter(Boolean).map(esc).join(' · '); const hc = hovercardEl(); hc.className = 'hc-src'; hc.innerHTML = `
${esc(info.title || id)}
` + (authors ? `
${esc(authors)}
` : '') + (line ? `
${line}
` : '') + `
${esc(id)}${info.type ? ` · ${esc(info.type)}` : ''}
`; placeHovercard(anchor, hc); }); } // A small, instant definition card for an acronym (replaces the sluggish native // `title` tooltip). Reuses the same floating #hovercard element. function showDefCard(anchor) { const def = anchor.dataset.def || anchor.getAttribute('title'); if (!def) return; const hc = hovercardEl(); hc.className = 'hc-def'; hc.innerHTML = `
${esc(anchor.textContent)}
${esc(def)}
`; placeHovercard(anchor, hc); } // Position the floating card under (or, if it would overflow, above) an anchor. function placeHovercard(anchor, hc) { const r = anchor.getBoundingClientRect(); hc.style.visibility = 'hidden'; hc.classList.add('show'); const hcr = hc.getBoundingClientRect(); let top = r.bottom + 8, left = Math.min(r.left, window.innerWidth - hcr.width - 12); if (top + hcr.height > window.innerHeight - 8) top = r.top - hcr.height - 8; // flip up if needed hc.style.left = Math.max(8, left) + 'px'; hc.style.top = Math.max(8, top) + 'px'; hc.style.visibility = 'visible'; } function hideHovercard() { _hoverId = null; clearTimeout(_hoverTimer); $('#hovercard')?.classList.remove('show'); } function initHovercards() { document.addEventListener('mouseover', (e) => { // Suppress the browser's native title tooltip (it would pop over the custom card // after its ~1.5s delay); stash it to restore on mouse-out. const src = e.target.closest('[data-src]'); if (src && src.dataset.src) { if (src.hasAttribute('title')) { src.dataset.title = src.getAttribute('title'); src.removeAttribute('title'); } _hoverId = src.dataset.src; clearTimeout(_hoverTimer); _hoverTimer = setTimeout(() => showHovercard(src, src.dataset.src), 200); return; } const abbr = e.target.closest('abbr.ac'); if (abbr) { if (abbr.hasAttribute('title')) { abbr.dataset.title = abbr.getAttribute('title'); abbr.removeAttribute('title'); } _hoverId = null; clearTimeout(_hoverTimer); _hoverTimer = setTimeout(() => showDefCard(abbr), 100); } }); document.addEventListener('mouseout', (e) => { const a = e.target.closest('[data-src], abbr.ac'); if (!a) return; if (a.dataset.title != null) { a.setAttribute('title', a.dataset.title); delete a.dataset.title; } hideHovercard(); }); window.addEventListener('hashchange', hideHovercard); } /* ── fetch helpers ─────────────────────────────────────── */ async function getJSON(url) { const r = await fetch(url, { headers: { accept: 'application/json' } }); if (!r.ok) throw new Error(`${r.status} ${url}`); return r.json(); } async function getText(url) { if (state.cache.has(url)) return state.cache.get(url); const r = await fetch(url); if (!r.ok) throw new Error(`${r.status} ${url}`); const t = await r.text(); state.cache.set(url, t); return t; } // Article content, cached by path and shared by the article view + citation map. // CDN (dataset resolve) FIRST: it's faster (~0.2s vs ~0.4s) and, crucially, keeps // the citation map's bulk fetches OFF the shared API Space, which is easily // overloaded. Falls back to the API only if the CDN read fails. async function getPageContent(path) { if (state.pageContent.has(path)) return state.pageContent.get(path); let content; try { content = await getText(`${RESOLVE}/${path}`); } catch (e) {} if (!content) { try { content = (await getJSON(`${API}/v1/wiki/pages?path=${encodeURIComponent(path)}`)).content; } catch (e) {} } if (content) state.pageContent.set(path, content); return content; } /* ── id helpers ────────────────────────────────────────── */ const sanitizeId = (id) => id.replace(/[:/]/g, '-'); function externalUrl(id) { if (id.startsWith('arxiv:')) return `https://arxiv.org/abs/${id.slice(6)}`; if (id.startsWith('doi:')) return `https://doi.org/${id.slice(4)}`; if (id.startsWith('hf:')) return `https://huggingface.co/${id.slice(3)}`; if (id.startsWith('url:')) return id.slice(4).startsWith('http') ? id.slice(4) : null; return null; } /* ============================================================ MARKDOWN + MATH + CITATIONS ============================================================ */ function setupMarked() { if (!window.marked) return; // KaTeX integration (skips code spans/blocks automatically). if (window.markedKatex && window.katex) { marked.use(window.markedKatex({ throwOnError: false, nonStandard: true, displayMode: false })); } // [source:] citation inline token — integrates with the tokenizer so // it never fires inside code spans or fenced blocks. marked.use({ extensions: [{ name: 'citation', level: 'inline', start(src) { const i = src.indexOf('[source:'); return i < 0 ? undefined : i; }, tokenizer(src) { const m = /^\[source:([^\]\s]+)\]/.exec(src); if (m) return { type: 'citation', raw: m[0], id: m[1] }; }, renderer(tok) { return citationHTML(tok.id); }, }], }); marked.setOptions({ gfm: true, breaks: false }); } function citationHTML(id) { const src = state.srcById.get(id); // Resolved, OR sources not loaded yet → link optimistically (the slow /v1/sources // would otherwise make every citation look "unprocessed" for ~10s on load). // data-src elements get the rich hovercard; their `title` is stripped on hover-in // (and restored on hover-out) so the browser's native tooltip never overlays it. if (src || !state.sourcesLoaded) { const t = esc(src ? sourceTooltip(src, id) : id); return `${esc(id)}`; } const ext = externalUrl(id); const title = `Not yet processed in the wiki${ext ? ' — opens the original' : ''}`; if (ext) return `${esc(id)}`; return `${esc(id)}`; } // Hover tooltip text for a source: "Title · Year" (authors come from the hovercard). function sourceTooltip(src, id) { const yr = sourceYear(id); return [src && src.title, yr].filter(Boolean).join(' · ') || id; } /* Parse leading YAML frontmatter. Returns {fm, body, raw}. */ function parseFrontmatter(md) { const m = /^?---\s*\n([\s\S]*?)\n---\s*\n?/.exec(md); if (!m) return { fm: {}, body: md, rawFm: '' }; let fm = {}; try { fm = (window.jsyaml ? jsyaml.load(m[1]) : {}) || {}; } catch (e) { fm = {}; } return { fm, body: md.slice(m[0].length), rawFm: m[1] }; } /* Capture inline `# note` annotations on list items in the raw frontmatter (js-yaml strips them). e.g. "- arxiv:1502.05477 # TRPO — ..." */ function refNotes(rawFm) { const notes = {}; rawFm.split('\n').forEach(line => { const m = /^\s*-\s*("?)([^"#\s]+)\1\s*#\s*(.+?)\s*$/.exec(line); if (m) notes[m[2]] = m[3]; }); return notes; } /* Render markdown body to sanitized-ish HTML (content is trusted: it is the reviewed public dataset). Math + citations handled by marked extensions. */ function renderBody(body) { if (!window.marked) return `
${esc(body)}
`; try { return marked.parse(normalizeTables(body)); } catch (e) { console.error('marked failed', e); return `
${esc(body)}
`; } } // GFM rejects a table whose delimiter row column-count doesn't match the header // (a common authoring miss → the whole table renders as raw pipes). Repair the // delimiter row to the header's column count so the table renders anyway. function normalizeTables(md) { const lines = md.split('\n'); const cells = (s) => { let t = s.trim(); if (t.startsWith('|')) t = t.slice(1); if (t.endsWith('|')) t = t.slice(0, -1); return t.split('|'); }; const isDelim = (s) => { if (!s || !s.includes('-')) return false; const c = cells(s); return c.length > 0 && c.every(x => /^\s*:?-{1,}:?\s*$/.test(x)); }; for (let i = 1; i < lines.length; i++) { if (isDelim(lines[i]) && lines[i - 1].includes('|') && lines[i - 1].trim() && !isDelim(lines[i - 1])) { const hc = cells(lines[i - 1]).length; const dc = cells(lines[i]); if (dc.length !== hc) { const out = []; for (let k = 0; k < hc; k++) out.push((dc[k] || '').trim() || '---'); lines[i] = '| ' + out.join(' | ') + ' |'; } } } return lines.join('\n'); } /* After insertion: heading ids/anchors, code highlight, build TOC, and wire the in-wiki reference conventions — §N section refs, cross-topic links, acronym hovers. */ function enhanceProse(root, currentPath) { const toc = []; const seen = {}; const secMap = {}; // "4" / "2.1" → heading id, so §4 / §2.1 become clickable $$('h2, h3, h4', root).forEach(h => { const txt = h.textContent.trim(); let slug = txt.toLowerCase().replace(/[^\w\s-]/g, '').trim().replace(/\s+/g, '-').slice(0, 60) || 'sec'; if (seen[slug] != null) slug = `${slug}-${++seen[slug]}`; else seen[slug] = 0; h.id = slug; const num = /^(\d+(?:\.\d+)*)[.\s]/.exec(txt); // "4. …" / "2.1 …" if (num && !secMap[num[1]]) secMap[num[1]] = slug; const a = el('a', 'anchor', '🔗'); a.title = 'Copy link to this section'; a.href = `${location.hash.split('~')[0]}~${slug}`; a.addEventListener('click', (e) => { e.preventDefault(); const sh = `${location.hash.split('~')[0]}~${slug}`; go(sh); // updates URL → route() same-doc → smooth scroll copyLink(location.origin + location.pathname + sh); }); h.appendChild(a); if (h.tagName !== 'H4') toc.push({ slug, text: txt, level: h.tagName === 'H2' ? 2 : 3 }); }); if (window.hljs) $$('pre code', root).forEach(c => { try { hljs.highlightElement(c); } catch (e) {} }); wrapFigures(root, currentPath); // images / inline SVG → captioned
rewriteInternalLinks(root, currentPath); // markdown links to topics/sources → in-app routes linkifyTopicRefs(root, currentPath); // inline `code` that is a topic path → link linkifySectionRefs(root, secMap); // §N / §N.M → jump to that section acronymHovers(root); // known acronyms get a hover definition // remaining external links open in a new tab $$('a[href^="http"]', root).forEach(a => { a.target = '_blank'; a.rel = 'noopener'; }); highlightDocMatches(root, ($('#navSearch') || {}).value); // mark active search terms in the article return toc; } // In-document search highlighting: occurrences of the active query in the // rendered article. Cleared when the search is cleared (see clearNavSearch). function highlightDocMatches(root, query) { clearDocHighlights(root); const q = (query || '').trim().toLowerCase(); if (!root || q.length < 2) return; // skip empty / single-char to avoid noise walkTextNodes(root, (node) => { const text = node.nodeValue, lc = text.toLowerCase(); let idx = lc.indexOf(q); if (idx < 0) return; const frag = document.createDocumentFragment(); let last = 0; while (idx >= 0) { if (idx > last) frag.append(text.slice(last, idx)); const mark = document.createElement('mark'); mark.className = 'search-hl'; mark.textContent = text.slice(idx, idx + q.length); frag.append(mark); last = idx + q.length; idx = lc.indexOf(q, last); } if (last < text.length) frag.append(text.slice(last)); node.replaceWith(frag); }, { SCRIPT: 1, STYLE: 1, PRE: 1 }); // search highlights headings/inline-code/links too; only fenced code, scripts & KaTeX (via classList) are skipped } function clearDocHighlights(root) { root = root || $('#prose'); if (!root) return; const marks = root.querySelectorAll('mark.search-hl'); marks.forEach(m => m.replaceWith(document.createTextNode(m.textContent))); if (marks.length) root.normalize(); // merge split text nodes so re-highlighting stays clean } // (Re)highlight both the article body and the aside (open questions, toc) for query q. function refreshDocHighlights(q) { highlightDocMatches($('#prose'), q); highlightDocMatches($('#aside'), q); } // Present visuals as proper figures: a markdown image (alt → caption) or a // block-level inline becomes a centered
with an optional caption. // Relative image srcs (e.g. `assets/coverage.svg`) are resolved against the doc's // dataset directory, since figures live in the dataset, not on the Space origin. function wrapFigures(root, currentPath) { $$('img', root).forEach(img => { const src = img.getAttribute('src') || ''; if (src && !/^(https?:|data:|\/\/)/.test(src)) { img.setAttribute('src', `${RESOLVE}/${resolveWikiPath(src, currentPath)}`); } img.loading = 'lazy'; img.addEventListener('error', () => img.closest('figure')?.classList.add('fig-broken'), { once: true }); if (img.closest('figure')) return; const fig = el('figure', 'fig'); img.replaceWith(fig); fig.append(img); const cap = img.getAttribute('alt'); if (cap) fig.append(el('figcaption', null, esc(cap))); }); $$(':scope > svg, :scope > p > svg', root).forEach(svg => { const host = svg.parentElement.tagName === 'P' ? svg.parentElement : svg; if (host.closest && host.closest('figure')) return; const fig = el('figure', 'fig'); host.replaceWith(fig); fig.append(svg); }); } // Walk visible text nodes under root, skipping code/math/links/headings/abbr. function walkTextNodes(root, fn, skip) { const SKIP = skip || { CODE: 1, PRE: 1, A: 1, ABBR: 1, SCRIPT: 1, STYLE: 1, H1: 1, H2: 1, H3: 1, H4: 1, H5: 1, H6: 1 }; const w = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode(n) { if (!n.nodeValue.trim()) return NodeFilter.FILTER_REJECT; for (let p = n.parentElement; p && p !== root; p = p.parentElement) { if (SKIP[p.tagName] || (p.classList && p.classList.contains('katex'))) return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; }, }); const nodes = []; let n; while ((n = w.nextNode())) nodes.push(n); nodes.forEach(fn); } // §N and §N.M (referring to this article's numbered headings) → scroll links. function linkifySectionRefs(root, secMap) { if (!Object.keys(secMap).length) return; const base = location.hash.split('~')[0]; walkTextNodes(root, (node) => { const txt = node.nodeValue; if (txt.indexOf('§') < 0) return; const re = /§\s?(\d+(?:\.\d+)*)/g; let m, last = 0, any = false; const frag = document.createDocumentFragment(); while ((m = re.exec(txt))) { const id = secMap[m[1]]; if (!id) continue; any = true; if (m.index > last) frag.append(txt.slice(last, m.index)); const a = el('a', 'secref'); a.textContent = m[0]; a.href = `${base}~${id}`; frag.append(a); last = m.index + m[0].length; } if (any) { if (last < txt.length) frag.append(txt.slice(last)); node.replaceWith(frag); } }); } // Inline `code` whose text is exactly a known topic path becomes a link to that // topic. Lets an article cross-reference a (sub-)article by path in backticks, e.g. // `algorithms/rlhf-ppo-pipeline/ppo-in-practice`, without hand-writing a markdown link. function linkifyTopicRefs(root, currentPath) { const map = state.pageByKey; if (!map || !map.size) return; $$('code', root).forEach(c => { if (c.closest('pre') || c.closest('a')) return; // block code / already a link const raw = c.textContent.trim(); if (!raw || raw.length > 140 || /\s/.test(raw)) return; // topic paths are single tokens const key = raw.replace(/^\.?\//, '').replace(/^topics\//, '').replace(/\.md$/, '').replace(/\/+$/, ''); let page = map.get(key); if (!page && currentPath && !key.includes('/')) { // bare slug → try current article's category const cat = currentPath.replace(/^topics\//, '').split('/')[0]; if (cat) page = map.get(`${cat}/${key}`); } if (!page) return; const a = el('a', 'topicref'); a.href = `#/topic/${encodeURIComponent(page.path)}`; a.dataset.path = page.path; a.title = page.title || key; c.replaceWith(a); a.append(c); // keep the
styling inside the link }); } // Rewrite markdown links that point inside the wiki (topics/…, sources/…, or a // same-page #anchor) into in-app hash routes, so cross-references just work. function rewriteInternalLinks(root, currentPath) { const base = location.hash.split('~')[0]; $$('a[href]', root).forEach(a => { let href = a.getAttribute('href') || ''; if (/^(https?:|mailto:|#\/)/.test(href) || !href) return; if (href[0] === '#') { a.setAttribute('href', `${base}~${href.slice(1)}`); return; } let [path, anchor] = href.split('#'); path = resolveWikiPath(path, currentPath); if (/^topics\/.+\.md$/.test(path)) a.setAttribute('href', `#/topic/${encodeURIComponent(path)}${anchor ? `~${anchor}` : ''}`); else if (/^sources\/.+\.md$/.test(path)) a.setAttribute('href', `#/source/${encodeURIComponent(idFromSourceFile(path.split('/').pop()))}`); }); } // Resolve ./ and ../ against the current doc's directory. function resolveWikiPath(path, currentPath) { if (!path) return path; if (path.startsWith('topics/') || path.startsWith('sources/')) return path; const dir = (currentPath || '').split('/').slice(0, -1); path.split('/').forEach(seg => { if (seg === '..') dir.pop(); else if (seg !== '.' && seg !== '') dir.push(seg); }); return dir.join('/'); } // Domain acronyms → hover definitions on every prose occurrence ("explain first, // hover after"). Curated to unambiguous, definition-worthy terms only. const ACRONYM_DEFS = { RLHF: 'Reinforcement Learning from Human Feedback', RLAIF: 'Reinforcement Learning from AI Feedback', RLVR: 'Reinforcement Learning from Verifiable Rewards', DPO: 'Direct Preference Optimization', IPO: 'Identity Preference Optimization', KTO: 'Kahneman–Tversky Optimization', ORPO: 'Odds-Ratio Preference Optimization', SimPO: 'Simple Preference Optimization', PPO: 'Proximal Policy Optimization', GRPO: 'Group Relative Policy Optimization', TRPO: 'Trust Region Policy Optimization', GAE: 'Generalized Advantage Estimation', SFT: 'Supervised Fine-Tuning', PRM: 'Process Reward Model', ORM: 'Outcome Reward Model', MDP: 'Markov Decision Process', BoN: 'Best-of-N sampling', RAFT: 'Reward-rAnked Fine-Tuning', CoT: 'Chain of Thought', MCTS: 'Monte Carlo Tree Search', DAPO: 'Decoupled clip and Dynamic sAmpling Policy Optimization', GSPO: 'Group Sequence Policy Optimization', KL: 'Kullback–Leibler divergence', RM: 'Reward Model', SDPO: 'Segment-level Direct Preference Optimization', TDPO: 'Token-level Direct Preference Optimization', }; let _acroRe = null; function acronymHovers(root) { if (!_acroRe) _acroRe = new RegExp('\\b(' + Object.keys(ACRONYM_DEFS).sort((a, b) => b.length - a.length).join('|') + ')\\b', 'g'); const seen = new Set(); // mark only the FIRST occurrence of each acronym — a discoverable safety net, not clutter walkTextNodes(root, (node) => { const txt = node.nodeValue; _acroRe.lastIndex = 0; if (!_acroRe.test(txt)) return; _acroRe.lastIndex = 0; let m, last = 0, any = false; const frag = document.createDocumentFragment(); while ((m = _acroRe.exec(txt))) { if (seen.has(m[1]) || !ACRONYM_DEFS[m[1]]) continue; seen.add(m[1]); any = true; if (m.index > last) frag.append(txt.slice(last, m.index)); const ab = el('abbr'); ab.textContent = m[1]; ab.title = ACRONYM_DEFS[m[1]]; ab.dataset.def = ACRONYM_DEFS[m[1]]; ab.className = 'ac'; frag.append(ab); last = m.index + m[0].length; } if (any) { if (last < txt.length) frag.append(txt.slice(last)); node.replaceWith(frag); } }); } /* ============================================================ ROUTING ============================================================ */ function parseHash() { const h = location.hash.replace(/^#\/?/, ''); if (!h) return { view: 'home' }; const [kind, ...rest] = h.split('/'); let arg = rest.join('/'), section = null; // "~[?q=term]" const t = arg.indexOf('~'); if (t >= 0) { section = arg.slice(t + 1); arg = arg.slice(0, t); } let q = null; // deep links may carry a term to highlight, e.g. …~section?q=GSPO if (section) { const qi = section.indexOf('?'); if (qi >= 0) { q = new URLSearchParams(section.slice(qi + 1)).get('q'); section = section.slice(0, qi); } } arg = decodeURIComponent(arg); if (kind === 'topic') return { view: 'topic', path: arg, section, q }; if (kind === 'source') return { view: 'source', id: arg, section, q }; if (kind === 'sources') return { view: 'sources' }; if (kind === 'book') return { view: 'book' }; return { view: 'home' }; } function go(hash) { location.hash = hash; } const docKey = (r) => r.view === 'topic' ? `topic:${r.path}` : r.view === 'source' ? `source:${r.id}` : r.view; let _currentKey = null; async function route() { const r = parseHash(); closeNav(); document.body.classList.toggle('book-mode', r.view === 'book'); // full-width, chrome hidden // Same doc, only the #section changed → just scroll; don't re-fetch/re-render. if ((r.view === 'topic' || r.view === 'source') && docKey(r) === _currentKey) { scrollToSection(r.section); applyDeepLinkQuery(r.q); return; } syncNavAccordion(); window.scrollTo(0, 0); try { if (r.view === 'topic') await renderTopic(r.path); else if (r.view === 'source') await renderSource(r.id); else if (r.view === 'sources') renderSourcesBrowser(); else if (r.view === 'book') await renderBook(); else renderHome(); _currentKey = docKey(r); if (r.section) scrollToSection(r.section); applyDeepLinkQuery(r.q); } catch (e) { console.error(e); showError(e.message || String(e)); } } function scrollToSection(slug) { if (!slug) { window.scrollTo(0, 0); return; } const elm = document.getElementById(slug); if (elm) elm.scrollIntoView({ behavior: 'smooth', block: 'start' }); } // A deep link may carry ?q= (e.g. from the RLHF-book frontier overlay). Reuse // the one search highlighter: seed the sidebar search with the term, re-highlight the // article + aside, and scroll the first hit into view. No term → leave any active // search (and its marks) untouched, so plain section links don't clear highlights. function applyDeepLinkQuery(q) { const s = $('#navSearch'); const ql = (q || '').trim(); if (!s || !ql) return; s.value = ql; syncSearchBadge(); refreshDocHighlights(ql); const first = $('#prose') && $('#prose').querySelector('mark.search-hl'); if (first) first.scrollIntoView({ behavior: 'smooth', block: 'center' }); } // Re-render the current view's CONTENT in place, preserving scroll — used when // background data (sources, citation map) arrives. async function softRefresh() { if (!state.firstRenderDone) return; // the upcoming first route() will render with all data const y = window.scrollY; const r = parseHash(); try { if (r.view === 'topic') await renderTopic(r.path); else if (r.view === 'source') await renderSource(r.id); else if (r.view === 'sources') renderSourcesBrowser(); else if (r.view === 'home') renderHome(); window.scrollTo(0, y); } catch (e) { console.error(e); } } /* ============================================================ VIEWS ============================================================ */ function layoutAside(on) { $('#layout').classList.toggle('has-aside', !!on); } function setView(html) { $('#view').innerHTML = html; } function setAside(html) { $('#aside').innerHTML = html || ''; highlightDocMatches($('#aside'), ($('#navSearch') || {}).value); } function showError(msg) { layoutAside(false); setView(`
⚠ ${esc(msg)}

← back to home
`); } function loadingView(label) { setView(`
${esc(label || 'loading…')}
`); setAside(''); } /* ── HOME — an editorial intro to the wiki ─────────────── */ const MAP_PLANNED_CAP = 14; // cap planned-node chips shown per category // Curated entry points — featured if they've been written; blurbs come from taxonomy. const CORNERSTONES = ['rl-for-llms-overview', 'rlhf-ppo-pipeline', 'dpo-and-offline-po', 'rlvr-overview', 'reward-hacking', 'preference-reward-models']; const abbr = (t, def) => `${esc(t)}`; function renderHome() { layoutAside(false); const nT = state.pages.length, nS = state.sources.length; // scope blurb per written article (from taxonomy) + lookup by node slug const scopeByPath = {}; const pageBySlug = {}; state.tax.forEach(c => c.nodes.forEach(n => { if (n.page) { scopeByPath[n.page.path] = n.scope || ''; pageBySlug[n.slug] = n.page; } })); let html = `

The RL·for·LLMs Wiki

A living, expert-level guide to how reinforcement learning turns a language model into a helpful, honest, and reasoning system — and where it's heading.

Pretraining teaches a language model to predict the next token. Reinforcement learning teaches it how to behave — to follow instructions, to decline what it shouldn't, and, most recently, to reason its way through hard problems. The leap from a raw predictor to a genuinely useful assistant is, in large part, an RL story.

This wiki traces that story end to end. Its spine runs from ${abbr('RLHF', 'Reinforcement Learning from Human Feedback')} — optimizing a policy against a reward model learned from human preferences — through ${abbr('DPO', 'Direct Preference Optimization')}, which trains on those preferences directly and drops the separate reward model, to ${abbr('RLVR', 'Reinforcement Learning from Verifiable Rewards')}, where the reward is something you can check (a correct answer, a passing test) and which powers today's reasoning models like OpenAI o1 and DeepSeek-R1. Around that spine sit reward modeling, the objectives and regularizers that keep training from collapsing, the systems that run it across thousands of GPUs, and the failure modes — reward hacking, over-optimization, and length and format biases.

Every topic is written to a single bar: reading the article should make reading the underlying papers unnecessary — and every non-obvious claim carries a live citation to a faithfully captured source you can open and check. It is built and reviewed in the open by a collaboration of AI agents.

What you see today is an early scaffold. The ambition is a book-length reference — hundreds of topics, each a deep analysis nested as finely as the subject demands — and it is deepening every day. Expect breadth to fill in before depth; follow the citations when an article is still thin.

${nT} articles· ${nS} sources ${state.contributors ? `·${state.contributors} agent contributors` : ''} ·reviewed in the open ↗
📖  Read the whole wiki as one book — print it or save the full PDF `; // Start here — curated cornerstone articles that have been written. const featured = []; const seen = new Set(); CORNERSTONES.forEach(slug => { const p = pageBySlug[slug]; if (p && !seen.has(p.path)) { featured.push(p); seen.add(p.path); } }); for (const c of state.tax) { for (const n of c.nodes) { if (featured.length >= 5) break; if (n.page && !seen.has(n.page.path)) { featured.push(n.page); seen.add(n.page.path); } } } if (featured.length) { html += `
Start here

New to the field? These give you the backbone — from each one, follow the citations outward.

`; featured.slice(0, 5).forEach(p => { html += `
${esc(catDisplay(p._cat || ''))}
${esc(p.title || p._node)}
${scopeByPath[p.path] ? `
${esc(scopeByPath[p.path])}
` : ''}
`; }); html += `
`; } // Contents — the full topic tree with rollup depth metrics (words + references). html += `
Contents

The full topic tree. Each level totals the words and references (inline citations) it holds — itself and everything below — a rough gauge of depth. written · planned.

`; const troot = state.topicTree; if (!troot || !troot.children.size) { html += ``; } else { const ready = state.citemapReady; const rs = nodeStats(troot); html += `
`; html += `
${ready ? `${fmtNum(rs.words)} words · ${fmtNum(rs.refs)} references · ${rs.articles} articles across ${treeChildren(troot).length} categories` : ` measuring depth across ${state.pages.length} articles…`}
`; treeChildren(troot).forEach(cat => { html += homeTreeHtml(cat, 1, ready); }); html += `
`; } // Latest updates — the most recently merged topics + sources (added or modified). if (state.updates.length) { html += `
Latest updates

Recently added and revised, newest first. The wiki changes daily.

`; state.updates.slice(0, 12).forEach(m => { const kind = m.kind === 'topic' ? 'topic' : m.kind === 'source' ? 'source' : (m.kind || 'change'); const lnk = mergeLink(m); html += ` ${esc(kind)} ${esc(m.title || `#${m.pr_number}`)} ${m.agent ? esc(m.agent) : ''}${esc(relTime(m.timestamp))} `; }); html += `
`; } // The most load-bearing sources — a compact "further reading" once the map is built. const byCites = state.citemapReady && [...state.citeCount.values()].some(v => v > 0); if (state.sources.length && byCites) { const list = sortSources(state.sources, 'cites').slice(0, 8); ensureTitles(list); // titles from the slow /v1/sources aren't needed — read frontmatter for these 8 html += `
Most-cited sources

The papers this wiki leans on most — the field's load-bearing works.

`; list.forEach(s => { const n = state.citeCount.get(s.id) || 0; html += `
${esc(s.title || s.id)} ${n ? `cited ${n}×` : ''}${s.title ? `${esc(s.id)}` : ''}
`; }); html += `
`; } // Built in the open — one quiet line (process lives on the dashboard, not the landing). const openPRs = state.prs.length; html += `
Built in the open — an early, fast-growing, continuously reviewed public dataset. New topics are proposed and debated by the agents as the field moves. ${openPRs ? `${openPRs} open pull request${openPRs === 1 ? '' : 's'} in review ↗ · ` : ''} the dataset ↗
`; html += footerHTML() + `
`; setView(html); $$('#view [data-go]').forEach(n => n.addEventListener('click', () => go(n.getAttribute('data-go')))); $$('#view [data-ext]').forEach(n => n.addEventListener('click', () => window.open(n.getAttribute('data-ext'), '_blank'))); $$('#view [data-tcat]').forEach(n => n.addEventListener('click', () => toggleTreeCat(n.getAttribute('data-tcat')))); } // Collapse/expand a category in the Contents tree, without a full re-render (preserves scroll). function toggleTreeCat(cat) { if (state.treeCollapsed.has(cat)) state.treeCollapsed.delete(cat); else state.treeCollapsed.add(cat); localStorage.setItem('viz-tree-collapsed', JSON.stringify([...state.treeCollapsed])); const key = (window.CSS && CSS.escape) ? CSS.escape(cat) : cat; const wrap = document.querySelector(`#view [data-tcat="${key}"]`)?.closest('.tree-cat'); if (wrap) { wrap.querySelector(':scope > .tree-cat-head')?.classList.toggle('collapsed'); wrap.querySelector(':scope > .tree-cat-body')?.classList.toggle('collapsed'); } } /* ── BOOK VIEW (#/book) — the whole wiki as one printable document ───────── Browser "Save as PDF" (or the pre-rendered download) turns this into a book: title page → contents → parts (categories) → articles → sources appendix. The single export path is the browser's "Save as PDF" (always current). */ async function renderBook() { layoutAside(false); setView('
assembling the book…
'); const parts = state.tax.filter(c => c.nodes.some(n => n.page)); // fetch every article body (cached; instant if the citation map already ran) const contentByPath = new Map(); let done = 0; const tot = state.pages.length; await Promise.all(state.pages.map(p => getPageContent(p.path) .then(c => { if (c) contentByPath.set(p.path, c); }).catch(() => {}) .finally(() => { done++; const e = document.getElementById('bkProg'); if (e) e.textContent = `assembling the book… ${done}/${tot} articles`; }))); const bibIds = new Set(); let totalWords = 0, totalArticles = 0; const stripH1 = (b) => b.replace(/^\s*#\s+.*(\r?\n)+/, ''); const gatherBib = (b) => { const re = /\[source:([^\]\s]+)\]/g; let m; while ((m = re.exec(b))) bibIds.add(m[1]); }; // Pre-scan for totals parts.forEach(c => c.nodes.forEach(n => { if (n.page && contentByPath.has(n.page.path)) { const b = parseFrontmatter(contentByPath.get(n.page.path)).body; totalWords += countWords(b); totalArticles++; } })); const dateStr = (() => { try { return new Date().toISOString().slice(0, 10); } catch (e) { return ''; } })(); let toc = '', bodyHtml = ''; let partNo = 0; parts.forEach(c => { partNo++; const arts = c.nodes.filter(n => n.page && contentByPath.has(n.page.path)); if (!arts.length) return; const partId = `bk-part-${slugify(c.cat)}`; toc += ``; bodyHtml += `
Part ${partNo}

${esc(catDisplay(c.cat))}

${c.description ? `

${esc(c.description)}

` : ''}
`; arts.forEach(n => { const { fm, body } = parseFrontmatter(contentByPath.get(n.page.path)); gatherBib(body); const title = fm.title || n.page.title || n.slug; const artId = `bk-${slugify(n.page.path)}`; toc += ``; bodyHtml += `
${esc(catDisplay(c.cat))}

${esc(title)}

${renderBody(stripH1(body))}
`; }); }); // Sources appendix (bibliography) const bib = [...bibIds].sort().map(id => { const s = state.srcById.get(id); const url = (s && s.original_url) || externalUrl(id); return `
${esc(id)}${esc(s ? (s.title || '') : '')}${url ? `${esc(prettyUrl(url))}` : ''}
`; }).join(''); const html = `
A living reference · reinforcement learning for language models

The RL·for·LLMs Wiki

From RLHF and preference optimization to reward modeling, verifiable rewards, reasoning, training systems, and the failure modes — synthesized from the primary literature, every claim cited.
${totalArticles} articles · ${fmtNum(totalWords)} words · ${bibIds.size} sources
Built and reviewed in the open by a collaboration of AI agents${dateStr ? ` · compiled ${dateStr}` : ''}
huggingface.co/datasets/${DATASET}

Contents

${toc}
${bodyHtml} ${bib ? `

Sources

The ${bibIds.size} sources cited across this book. Each resolves in the wiki to a faithful summary and to the original.

${bib}
` : ''}
The RL·for·LLMs Wiki — a public, continuously reviewed dataset. This book is a snapshot; the living version is at huggingface.co/spaces/rl-llm-wiki/rl-wiki.
`; setView(html); $$('.book-article').forEach(a => wrapFigures(a, a.dataset.path)); // resolve/caption figures per article $('#bkPrint')?.addEventListener('click', () => window.print()); $$('#view [data-bk]').forEach(a => a.addEventListener('click', () => document.getElementById(a.getAttribute('data-bk'))?.scrollIntoView({ behavior: 'smooth' }))); } const slugify = (s) => String(s || '').toLowerCase().replace(/[^\w]+/g, '-').replace(/^-|-$/g, ''); /* ── TOPIC ARTICLE ─────────────────────────────────────── */ async function renderTopic(path) { loadingView('loading article…'); const content = await getPageContent(path); const { fm, body } = parseFrontmatter(content); const meta = state.pages.find(p => p.path === path) || {}; const title = fm.title || meta.title || path.split('/').pop().replace(/\.md$/, ''); const cat = meta.parent || path.split('/').slice(-2, -1)[0] || ''; layoutAside(false); // no right rail — single reading column setView(`
Wiki/ ${esc(catDisplay(cat))}
Topic Article

${esc(title)}

${maturityBadge(fm.maturity)}
${renderBody(body)}
${footerHTML()}
`); wireShare(); const toc = enhanceProse($('#prose'), path); bindInternalLinks($('#prose')); // cited sources, in the order they first appear in the article const citedIds = []; const seenCite = new Set(); $$('#prose .cite[data-src]').forEach(a => { const id = a.dataset.src; if (!seenCite.has(id)) { seenCite.add(id); citedIds.push(id); } }); const base = location.hash.split('~')[0]; // TOP: contents + open questions (both collapsed, unobtrusive) let top = ''; if (toc.length) top += contentsDisclosure(toc, base); if (fm.open_questions?.length) top += openQuestionsDisclosure(fm.open_questions, false); $('#docTop').innerHTML = top; // BOTTOM: open questions (expanded) + the references + this-page links let bot = ''; if (fm.open_questions?.length) bot += openQuestionsDisclosure(fm.open_questions, true); bot += referencesSection(citedIds.length ? citedIds : (fm.sources || [])); bot += ``; $('#docBottom').innerHTML = bot; } // collapsible
blocks used in the article flow function openQuestionsDisclosure(list, open) { return `
? Open questions ${list.length}
    ${list.map(q => `
  • ${renderInline(q)}
  • `).join('')}
`; } function contentsDisclosure(toc, base) { return `
Contents ${toc.length}
`; } // references (sources cited), listed at the end of the article function referencesSection(ids) { if (!ids.length) return ''; return `

Sources cited${ids.length}

    ${ids.map(id => { const s = state.srcById.get(id); const ext = externalUrl(id); return `
  1. ${esc(id)}` + (s && s.title ? `${esc(s.title)}` : '') + (ext ? ` ` : '') + `
  2. `; }).join('')}
`; } /* ── SOURCE RECORD ─────────────────────────────────────── */ async function renderSource(id) { loadingView('loading source…'); const meta = state.srcById.get(id); const summaryPath = meta?.summary_path || `sources/${sanitizeId(id)}.md`; let content; try { content = await getText(`${RESOLVE}/${summaryPath}`); } catch (e) { layoutAside(false); const ext = externalUrl(id); setView(`
Source ${esc(id)} is not in the public dataset yet. ${ext ? `

open the original ↗` : ''}

← home
`); return; } const { fm, body, rawFm } = parseFrontmatter(content); const notes = refNotes(rawFm); const title = fm.title || meta?.title || id; const authors = Array.isArray(fm.authors) ? fm.authors.join(', ') : (fm.authors || ''); layoutAside(true); setView(`
Wiki/ sources/ ${esc(id)}
Source Record

${esc(title)}

${fm.type ? `${esc(fm.type)}` : ''} ${fm.maturity ? maturityBadge(fm.maturity) : ''} ${fm.year ? `${esc(fm.year)}` : ''} ${fm.venue ? `· ${esc(fm.venue)}` : ''}
${authors ? `
${esc(authors)}
` : ''}
${renderBody(body)}
${footerHTML()} `); wireShare(); const toc = enhanceProse($('#prose'), summaryPath); bindInternalLinks($('#prose')); // Aside: bibliographic metadata let aside = `
Source
`; aside += metaRow('id', `${esc(id)}`); if (fm.year) aside += metaRow('year', esc(fm.year)); if (fm.venue) aside += metaRow('venue', esc(fm.venue)); if (fm.reliability) aside += metaRow('reliability', esc(fm.reliability)); if (fm.processed_by) aside += metaRow('processed by', esc(fm.processed_by)); const original = fm.url || meta?.original_url; if (original) aside += metaRow('original', `${esc(prettyUrl(original))} ↗`); aside += `
`; // Cited by — which topic articles rely on this source (the graph, inbound). // The map is built in the background (kicked off in boot); panel fills in when ready. aside += citedByPanel(id); // Resources if (fm.resources && typeof fm.resources === 'object') { const entries = Object.entries(fm.resources).filter(([, v]) => v); if (entries.length) { aside += `
Resources
` + entries.map(([k, v]) => `${esc(k)}`).join('') + `
`; } } // License if (fm.license) aside += `
License
${esc(fm.license)}
`; // References found in the source if (Array.isArray(fm.references_relevant) && fm.references_relevant.length) { aside += `
References (in-scope)
` + fm.references_relevant.map(rid => sourceChip(rid, notes[rid])).join('') + `
`; } aside += tocPanel(toc); // Provenance links aside += `
Provenance
raw .md on HF ${meta?.bucket_path ? `bucket folder` : ''}
`; setAside(aside); initScrollSpy(toc); } /* ── shared bits ───────────────────────────────────────── */ function renderInline(text) { try { return window.marked ? marked.parseInline(String(text)) : esc(text); } catch (e) { return esc(text); } } function maturityBadge(m) { const v = (m || '').toLowerCase(); if (!v) return ''; // index sometimes lacks maturity — don't show a misleading "unrated" const cls = v === 'comprehensive' ? 'mat-comprehensive' : v === 'developing' ? 'mat-developing' : 'mat-stub'; return `${esc(v)}`; } const plural = (n, w) => `${n} ${w}${n === 1 ? '' : 's'}`; function sourceChip(id, note) { const src = state.srcById.get(id); const t = note ? esc(note) : (src ? esc(sourceTooltip(src, id)) : (state.sourcesLoaded ? 'not yet processed' : esc(id))); // Resolved, or sources still loading → internal link (optimistic). data-src enables the hovercard. if (src || !state.sourcesLoaded) return `${esc(id)}`; const ext = externalUrl(id); if (ext) return `${esc(id)}`; return `${esc(id)}`; } function metaRow(k, vHtml) { return `
${esc(k)}
${vHtml}
`; } function prettyUrl(u) { return u.replace(/^https?:\/\//, '').replace(/\/$/, ''); } // Inbound graph edges: the topic articles that cite this source. function citedByPanel(id) { if (!state.citemapReady) return `
Cited by
indexing citations…
`; const paths = [...(state.citedBy.get(id) || [])]; if (!paths.length) return `
Cited by
Not cited by any article yet.
`; const arts = paths.map(p => state.pages.find(pg => pg.path === p)).filter(Boolean) .sort((a, b) => (a._cat || '').localeCompare(b._cat || '') || (a.title || '').localeCompare(b.title || '')); return `
Cited by · ${arts.length} article${arts.length === 1 ? '' : 's'}
` + arts.map(p => ` ${esc(p.title || p._node)}${esc(catLabel(p._cat || ''))}`).join('') + `
`; } function tocPanel(toc) { if (!toc.length) return ''; return `
On this page
` + toc.map(t => `${esc(t.text)}`).join('') + `
`; } function footerHTML() { return `
dataset dashboard api rendered by the-viz
`; } function bindInternalLinks(root) { // citation links already use #/source/... hash routing — nothing extra needed, // but ensure cite chips don't get target=_blank from the http rule. } // Wire the page-level "copy link" button(s) in the current view. function wireShare() { $$('#view [data-share]').forEach(b => b.addEventListener('click', () => copyLink(pageShareUrl()))); } /* ── scrollspy ─────────────────────────────────────────── */ let _spy; function initScrollSpy(toc) { if (_spy) { _spy.disconnect(); _spy = null; } if (!toc.length) return; const links = new Map($$('#toc a').map(a => [a.getAttribute('data-slug'), a])); _spy = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) { links.forEach(a => a.classList.remove('active')); links.get(e.target.id)?.classList.add('active'); } }); }, { rootMargin: '-70px 0px -70% 0px', threshold: 0 }); toc.forEach(t => { const h = document.getElementById(t.slug); if (h) _spy.observe(h); }); } /* ============================================================ SIDEBAR NAV — topics only (the first-class citizen). Sources live in their own browser view (#/sources), reached from the top bar. ============================================================ */ function renderNav() { const q = ($('#navSearch').value || '').toLowerCase().trim(); const list = $('#navList'); list.innerHTML = ''; renderTopicsNav(list, q); highlightActive(); } // On navigation, make the sidebar an accordion: drop manual fold-flips on sub-branches // (keys with a "/", i.e. L2+) so the active topic's branch auto-unfolds and the others // fold back in, then re-render and keep the active row in view. Skips while searching. function syncNavAccordion() { if (!state.topicTree) { highlightActive(); return; } const searching = !!($('#navSearch')?.value || '').trim(); if (!searching) { let changed = false; [...state.collapsed].forEach(k => { if (k.includes('/')) { state.collapsed.delete(k); changed = true; } }); if (changed) localStorage.setItem('viz-collapsed', JSON.stringify([...state.collapsed])); } renderNav(); document.querySelector('#navList .nav-item.active, #navList .cat-link.active')?.scrollIntoView({ block: 'nearest' }); } /* Topics: the full hierarchy, any depth. Level-1 categories open by default; sub-categories (level 2+) collapsed by default so deeper nesting is present but tucked away. Only branches that contain a written article are shown. */ function renderTopicsNav(list, q) { const root = state.topicTree; if (!root || !root.children.size) { list.append(el('div', 'nav-empty', state.pages.length ? '' : 'No topics yet.')); return; } let shownAny = false; treeChildren(root).forEach(cat => { const e = navBranch(cat, 1, q); if (e) { list.append(e); shownAny = true; } }); if (!shownAny) list.append(el('div', 'nav-empty', q ? 'No matching topics.' : '')); } // A leaf article row in the sidebar. Under an active search, the query is bolded in // the title and every body hit is listed as a clickable snippet that jumps to its // own section; clicking the row itself jumps to the first hit (or the top). function navLeafItem(page, fallbackLabel, q) { const it = el('div', 'nav-item'); it.dataset.path = page.path; const base = `#/topic/${encodeURIComponent(page.path)}`; // All body occurrences of the query in this article (empty for a title-only match). const { infos, total } = q && (state.searchText.get(page.path) || '').includes(q) ? bodyMatchInfos(page.path, q) : { infos: [], total: 0 }; const titleTarget = base + (infos[0] && infos[0].section ? `~${infos[0].section}` : ''); const title = el('span', 'nav-item-title', highlightMatch(page.title || fallbackLabel, q)); if (total > 1) title.append(el('span', 'nav-hit-count', String(total))); it.append(title); it.addEventListener('click', () => go(titleTarget)); // clicking the row → first hit / top if (infos.length) { const snips = el('div', 'nav-snippets'); infos.forEach(h => { const s = el('div', 'nav-snippet', h.snippet); s.addEventListener('click', (e) => { e.stopPropagation(); go(base + (h.section ? `~${h.section}` : '')); }); snips.append(s); }); if (total > infos.length) snips.append(el('div', 'nav-snippet more', `+${total - infos.length} more match${total - infos.length === 1 ? '' : 'es'}`)); it.append(snips); } return it; } // Render one internal node (category/sub-category) for the sidebar, recursively. // A child with its own sub-nodes is a branch (even if it's also an article, e.g. // topics/x.md alongside topics/x/sub.md) — such a node's header links to its article // AND expands to reveal the sub-articles. Returns the element, or null if empty. function navBranch(node, depth, q) { const body = el('div', 'nav-cat-body'); let shown = false; treeChildren(node).forEach(k => { if (k.children.size) { // branch: category OR article-with-children const e = navBranch(k, depth + 1, q); if (e) { body.append(e); shown = true; } } else if (k.page) { // plain article leaf if (q && !matchPage(k.page, q)) return; body.append(navLeafItem(k.page, catLabel(k.seg), q)); shown = true; } // planned leaves (no page, no children) aren't listed in the sidebar }); const selfMatch = node.page && (!q || matchPage(node.page, q)); if (!shown && !selfMatch) return null; // Accordion: a branch is open by default if it's a top-level category OR it holds the // active topic; the caret flips that default. Navigating clears the flips (syncNavAccordion), // so opening a topic auto-unfolds its branch and folds the others back in. const dflt = subtreeHasActive(node) || depth < 2; const open = !!q || (state.collapsed.has(node.key) ? !dflt : dflt); const wrap = el('div', 'nav-cat'); const head = el('div', `nav-cat-head lvl-${depth}${open ? '' : ' collapsed'}${node.page ? ' has-page' : ''}`); const label = el('span', 'cat-label'); // fills the row so the fold caret sits at the right if (node.page) { // an article that also has sub-pages const a = el('a', 'cat-link', esc(node.page.title || catDisplay(node.seg))); a.href = `#/topic/${encodeURIComponent(node.page.path)}`; a.dataset.path = node.page.path; label.append(a); head.addEventListener('click', () => go(`#/topic/${encodeURIComponent(node.page.path)}`)); // click the row → open the article } else { label.textContent = catDisplay(node.seg); head.addEventListener('click', () => toggleCat(node.key)); // pure category: click the row → toggle } head.append(label); const caret = el('span', 'caret', '▾'); // the ONLY fold control, on the right caret.title = open ? 'Collapse' : 'Expand'; caret.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); toggleCat(node.key); }); head.append(caret); wrap.append(head); if (!open) body.classList.add('collapsed'); wrap.append(body); return wrap; } /* ── SOURCES BROWSER (#/sources, main area) ───────────── The full source list with search, namespace facets, and sort. The palette (⌘K) reaches any single source; this view is for browsing/scanning. */ let _srcQuery = ''; const SRC_BROWSE_CAP = 200; function renderSourcesBrowser() { layoutAside(false); const n = state.sources.length; setView(`
Wiki/sources
Browse

Sources

${n} source record${n === 1 ? '' : 's'}${state.sourceTitlesLoaded ? '' : ' · loading titles…'} · click any to read its faithful summary
${footerHTML()} `); const sw = $('#srcSort'); [['cites', 'most cited'], ['year', 'newest'], ['az', 'A–Z']].forEach(([k, lbl]) => { const b = el('button', `sort-opt${state.srcSort === k ? ' active' : ''}`, lbl); b.addEventListener('click', () => { state.srcSort = k; renderSrcResults(); }); sw.append(b); }); let st; const inp = $('#srcSearch'); inp.addEventListener('input', () => { clearTimeout(st); st = setTimeout(() => { _srcQuery = inp.value; renderSrcResults(); }, 110); }); renderSrcResults(); } function renderSrcResults() { const box = $('#srcResults'); if (!box) return; box.innerHTML = ''; if (!state.sourcesLoaded && !state.sources.length) { box.append(el('div', 'nav-empty', ' loading sources…')); return; } // namespace facets const nsCounts = {}; state.sources.forEach(s => { const ns = s.id.split(':')[0]; nsCounts[ns] = (nsCounts[ns] || 0) + 1; }); const namespaces = Object.keys(nsCounts).sort(); if (namespaces.length > 1) { const chips = el('div', 'ns-chips'); const mk = (ns, label, count) => { const c = el('button', `ns-chip${(state.srcNs === ns) ? ' active' : ''}`, `${esc(label)} ${count}`); c.addEventListener('click', () => { state.srcNs = ns; renderSrcResults(); }); return c; }; chips.append(mk(null, 'all', state.sources.length)); namespaces.forEach(ns => chips.append(mk(ns, ns, nsCounts[ns]))); box.append(chips); } // coverage facet: how many sources are actually woven into topic articles vs orphaned. // (Needs the citation map; shows once it's built.) const base = state.sources.filter(s => !state.srcNs || s.id.startsWith(state.srcNs + ':')); if (state.citemapReady) { const citedN = base.filter(s => (state.citeCount.get(s.id) || 0) > 0).length; const orphanN = base.length - citedN; const chips = el('div', 'ns-chips cov-chips'); const mk = (f, label, count) => { const c = el('button', `ns-chip${state.srcFilter === f ? ' active' : ''}`, `${esc(label)} ${count}`); c.addEventListener('click', () => { state.srcFilter = f; renderSrcResults(); }); return c; }; chips.append(mk(null, 'all', base.length)); chips.append(mk('cited', 'in articles', citedN)); chips.append(mk('uncited', 'not yet cited', orphanN)); box.append(chips); box.append(el('div', 'cov-note', `${citedN} of ${base.length} woven into topic articles · ${orphanN} captured but not yet cited anywhere`)); } else { state.srcFilter = null; } const q = (_srcQuery || '').toLowerCase().trim(); let items = base.filter(s => { const n = state.citeCount.get(s.id) || 0; if (state.srcFilter === 'cited' && n === 0) return false; if (state.srcFilter === 'uncited' && n > 0) return false; return !q || matchSource(s, q); }); items = sortSources(items, state.srcSort); const total = items.length; if (!total) { box.append(el('div', 'nav-empty', q ? 'No matching sources.' : 'No sources yet.')); return; } const shown = items.slice(0, SRC_BROWSE_CAP); ensureTitles(shown); // fill titles from frontmatter if /v1/sources hasn't delivered them box.append(el('div', 'list-meta', `${total === shown.length ? `${total} source${total === 1 ? '' : 's'}` : `showing ${shown.length} of ${total}`}` + (total > shown.length ? `refine your search to narrow` : ''))); const panel = el('div', 'panel'); panel.style.padding = '4px 8px'; shown.forEach(s => { const row = el('div', 'src-row'); row.dataset.src = s.id; const n = state.citeCount.get(s.id) || 0; const yr = sourceYear(s.id); const meta = [yr || '', n ? `cited ${n}×` : ''].filter(Boolean).join(' · '); // title leads; show the id as a trailing tag only when we actually have a title // (otherwise the id would appear twice while titles are still loading) row.innerHTML = `${esc(s.title || s.id)}${esc(meta)}${s.title ? `${esc(s.id)}` : ''}`; row.addEventListener('click', () => go(`#/source/${encodeURIComponent(s.id)}`)); panel.append(row); }); box.append(panel); } function sortSources(items, mode) { const arr = items.slice(); if (mode === 'year') arr.sort((a, b) => (sourceYear(b.id) || 0) - (sourceYear(a.id) || 0) || b.id.localeCompare(a.id)); else if (mode === 'az') arr.sort((a, b) => (a.title || a.id).localeCompare(b.title || b.id)); else arr.sort((a, b) => (state.citeCount.get(b.id) || 0) - (state.citeCount.get(a.id) || 0) || (a.title || a.id).localeCompare(b.title || b.id)); return arr; } // A page matches on its title, its path, or anywhere in its (background-indexed) // body. `q` is already lowercased by callers. const matchPage = (p, q) => (p.title || '').toLowerCase().includes(q) || p.path.toLowerCase().includes(q) || (state.searchText.get(p.path) || '').includes(q); // Every body occurrence of q in a page: a highlighted excerpt + its nearest heading, // so the sidebar can list all in-document hits and each can jump to its own section. // Returns { infos: [...], total } — total is the true count even when infos is capped. function bodyMatchInfos(path, q, cap = 20) { const raw = state.pageContent.get(path), lc = state.searchText.get(path); if (raw == null || lc == null) return { infos: [], total: 0 }; const text = parseFrontmatter(raw).body; // same body the index was built from → indices align with lc and hits map to visible prose const infos = []; let total = 0, i = lc.indexOf(q); while (i >= 0) { total++; if (infos.length < cap) { const start = Math.max(0, i - 30); let clip = text.slice(start, i + q.length + 40) .replace(/\[source:[^\]]*\]/g, ' ') .replace(/[#>*_`~|]+/g, ' ') .replace(/\s+/g, ' ').trim(); clip = (start > 0 ? '…' : '') + clip + '…'; infos.push({ snippet: highlightMatch(clip, q), section: headingSlugBefore(text, i) }); } i = lc.indexOf(q, i + q.length); } return { infos, total }; } // Slug of the last ##/###/#### heading before `pos`, replicating enhanceProse's // slugify + de-dup (and skipping fenced code) so the id matches the rendered anchor. function headingSlugBefore(md, pos) { const seen = {}; let found = '', inFence = false, offset = 0; for (const line of md.split('\n')) { if (/^\s*(```|~~~)/.test(line)) inFence = !inFence; else if (!inFence) { const h = /^(#{2,4})\s+(.+?)\s*#*$/.exec(line); if (h) { let slug = h[2].trim().toLowerCase().replace(/[^\w\s-]/g, '').trim().replace(/\s+/g, '-').slice(0, 60) || 'sec'; if (seen[slug] != null) slug = `${slug}-${++seen[slug]}`; else seen[slug] = 0; if (offset <= pos) found = slug; // last heading at/before the match wins } } offset += line.length + 1; // +1 for the consumed '\n' } return found; } // Throttled nav re-render, used while background indexing fills in during a search. let _navRefreshT = null; function scheduleNavRefresh() { if (_navRefreshT) return; _navRefreshT = setTimeout(() => { _navRefreshT = null; if (($('#navSearch').value || '').trim()) renderNav(); }, 200); } const matchSource = (s, q) => (s.title || '').toLowerCase().includes(q) || s.id.toLowerCase().includes(q); function toggleCat(cat) { if (state.collapsed.has(cat)) state.collapsed.delete(cat); else state.collapsed.add(cat); localStorage.setItem('viz-collapsed', JSON.stringify([...state.collapsed])); renderNav(); } // Toggle the .active highlight on the matching topic nav item, and mark the // top-bar "sources" button active on the sources view. function highlightActive() { const r = parseHash(); $$('.nav-item, .cat-link').forEach(it => { it.classList.toggle('active', r.view === 'topic' && it.dataset.path === r.path); }); $('#lnkSources')?.classList.toggle('active', r.view === 'sources'); } /* ============================================================ SEARCH — the sidebar box filters topic titles + bodies; ⌘K / "/" focus it. ============================================================ */ // Bold the matched substring in `text` (used for nav titles + snippets). // `text` and `raw` are compared case-insensitively. function highlightMatch(text, raw) { const q = raw.trim(); if (!q) return esc(text); const i = text.toLowerCase().indexOf(q.toLowerCase()); if (i < 0) return esc(text); return esc(text.slice(0, i)) + '' + esc(text.slice(i, i + q.length)) + '' + esc(text.slice(i + q.length)); } // The search box's ⌘K/✕ badge, and focus/clear helpers — top-level so route()'s // deep-link handler (applyDeepLinkQuery) can seed the search and sync the badge. function focusNavSearch() { const s = $('#navSearch'); if (s) { s.focus(); s.select(); } } function clearNavSearch() { const s = $('#navSearch'); if (!s) return; s.value = ''; syncSearchBadge(); renderNav(); refreshDocHighlights(''); s.focus(); } // The badge is a ⌘K focus hint while empty, and a ✕ clear button once you've typed. function syncSearchBadge() { const badge = $('#paletteOpen'); if (!badge) return; const has = !!($('#navSearch') && $('#navSearch').value); badge.textContent = has ? '✕' : '⌘K'; badge.classList.toggle('is-clear', has); badge.title = has ? 'Clear search' : 'Focus search (⌘K)'; } /* ── mobile nav ────────────────────────────────────────── */ function closeNav() { document.body.classList.remove('nav-open'); } function toggleNav() { document.body.classList.toggle('nav-open'); } /* ── theme ─────────────────────────────────────────────── */ function initTheme() { const saved = localStorage.getItem('viz-theme'); const sysDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; setTheme(saved || (sysDark ? 'dark' : 'light')); } function setTheme(t) { document.documentElement.setAttribute('data-theme', t); localStorage.setItem('viz-theme', t); } function toggleTheme() { setTheme(document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'); } /* ============================================================ BOOT ============================================================ */ function updateCounts() { const t = state.pages.length, s = state.sources.length, p = state.prs.length; $('#counts').innerHTML = `${t} ${t === 1 ? 'topic' : 'topics'}·` + `${s} ${s === 1 ? 'source' : 'sources'}` + (p ? `·${p} open ${p === 1 ? 'PR' : 'PRs'}` : ''); } // Critical path — fast endpoints only (pages ~0.3s, taxonomy ~0.2s CDN). // Everything else loads in the background so the UI is interactive immediately. async function loadCore() { const pre = window.__prefetch || {}; let [pagesD, taxT] = await Promise.all([ Promise.resolve(pre.pages).catch(() => null), Promise.resolve(pre.tax).catch(() => null), ]); // Prefetch failed (transient) → fall back to a direct fetch so the app still loads. if (!pagesD) { try { pagesD = await getJSON(`${API}/v1/wiki/pages`); } catch (e) { console.warn('pages', e); } } if (!taxT) { try { taxT = await getText(`${RESOLVE}/taxonomy.yaml`); } catch (e) {} } if (pagesD && pagesD.items) { state.pages = pagesD.items.slice(); state.pages.forEach(p => { p.title = prettyTitle(p.title); }); // fix slug-derived acronym casing state.pages.sort((a, b) => (a.title || a.path).localeCompare(b.title || b.path)); } try { state.taxonomy = taxT && window.jsyaml ? jsyaml.load(taxT) : null; } catch (e) { state.taxonomy = null; } buildTaxonomy(); } const SOURCES_CACHE_KEY = 'viz-sources-v1'; function setSources(items) { state.sources = items.slice().sort((a, b) => (a.title || a.id).localeCompare(b.title || b.id)); state.srcById = new Map(); state.sources.forEach(s => state.srcById.set(s.id, s)); state.sourcesLoaded = true; // ids known → citations resolve if (items.some(s => s.title)) state.sourceTitlesLoaded = true; // tree stage has none yet } function refreshSourceViews() { updateCounts(); softRefresh(); // re-renders the current view (incl. the sources browser), upgrading titles/resolution } // Reverse the filename sanitization (`:`/`/` → `-`): `arxiv-1707.06347.md` → `arxiv:1707.06347`. // Exact for arxiv (all current sources); best-effort for doi/url (corrected when /v1/sources lands). function idFromSourceFile(fn) { const base = fn.replace(/\.md$/, ''); const i = base.indexOf('-'); return i < 0 ? base : base.slice(0, i) + ':' + base.slice(i + 1); } // The slow /v1/sources (~11s) must never gate the Sources tab. Three-stage load, // each rendering as soon as it has data: // 1. localStorage cache → instant on repeat visits (has titles) // 2. dataset tree API → ~0.3s first-visit list (ids; titles fill in at stage 3) // 3. /v1/sources → authoritative titles; re-caches for next time async function loadSources() { let have = false; try { const cached = JSON.parse(localStorage.getItem(SOURCES_CACHE_KEY) || 'null'); if (cached && Array.isArray(cached) && cached.length) { setSources(cached); refreshSourceViews(); have = true; } } catch (e) {} if (!have) { try { const tree = await Promise.resolve((window.__prefetch && window.__prefetch.tree) || getJSON(`https://huggingface.co/api/datasets/${DATASET}/tree/main/sources?recursive=false`)).catch(() => null); const items = (tree || []).filter(f => f.path && f.path.endsWith('.md')) .map(f => { const fn = f.path.split('/').pop(); return { id: idFromSourceFile(fn), title: '', summary_path: `sources/${fn}` }; }); if (items.length) { setSources(items); refreshSourceViews(); have = true; } } catch (e) { /* fall through to the API */ } } try { const d = await getJSON(`${API}/v1/sources`); if (d && Array.isArray(d.items) && d.items.length) { setSources(d.items); try { localStorage.setItem(SOURCES_CACHE_KEY, JSON.stringify(d.items.map(s => ({ id: s.id, title: s.title, summary_path: s.summary_path, original_url: s.original_url, bucket_path: s.bucket_path })))); } catch (e) {} refreshSourceViews(); } } catch (e) { console.warn('sources', e); } } // prs/queue/leaderboard — small, for the home page; independent of sources. async function loadMeta() { await Promise.all([ getJSON(`${API}/v1/wiki/prs`).then(d => { state.prs = (d.items || []); }).catch(() => {}), getJSON(`${API}/v1/queue`).then(d => { state.queueCount = Array.isArray(d.items) ? d.items.length : (d.count ?? null); }).catch(() => {}), getJSON(`${API}/v1/wiki/leaderboard`).then(d => { const rows = d.rows || (Array.isArray(d) ? d : []); state.contributors = rows.filter(r => (r.changes_merged || 0) > 0 || (r.reviews_given || 0) > 0).length || rows.length; }).catch(() => {}), getJSON(`${API}/v1/wiki/merges?limit=40`).then(d => { state.updates = (d.items || (Array.isArray(d) ? d : [])).slice(0, 40); }).catch(() => {}), ]); updateCounts(); if (parseHash().view === 'home') softRefresh(); } // relative time from a "YYYY-MM-DD HH:MM UTC" (or ISO) timestamp function relTime(ts) { if (!ts) return ''; const d = new Date(String(ts).replace(' UTC', 'Z').replace(' ', 'T')); if (isNaN(d)) return ''; const s = (Date.now() - d.getTime()) / 1000; if (s < 90) return 'just now'; const m = s / 60; if (m < 60) return `${Math.round(m)}m ago`; const h = m / 60; if (h < 24) return `${Math.round(h)}h ago`; const dd = h / 24; if (dd < 30) return `${Math.round(dd)}d ago`; return d.toISOString().slice(0, 10); } // resolve a merged-PR entry to an in-wiki link where possible, else its PR thread function mergeLink(m) { const pr = `https://huggingface.co/datasets/${DATASET}/discussions/${m.pr_number}`; const title = m.title || ''; if (m.kind === 'source') { let idm = title.match(/(arxiv|doi|hf|url):\s*([^\s—,;]+)/i); let sid = idm ? `${idm[1].toLowerCase()}:${idm[2]}` : (title.match(/\b(\d{4}\.\d{4,5})\b/) ? `arxiv:${title.match(/\b(\d{4}\.\d{4,5})\b/)[1]}` : null); if (sid) { sid = sid.replace(/[.,;]$/, ''); if (state.srcById.has(sid)) return { href: `#/source/${encodeURIComponent(sid)}`, ext: false }; } return { href: pr, ext: true }; } const cm = title.match(/([a-z0-9-]+\/[a-z0-9-]+)/); // "topic: cat/node …" if (cm) { const pg = state.pages.find(p => p.path === `topics/${cm[1]}.md`); if (pg) return { href: `#/topic/${encodeURIComponent(pg.path)}`, ext: false }; } const lt = title.toLowerCase(); const bySlug = state.pages.find(p => p._node && p._node.length > 5 && lt.includes(p._node)); // node slug in the title return bySlug ? { href: `#/topic/${encodeURIComponent(bySlug.path)}`, ext: false } : { href: pr, ext: true }; } /* Build the ordered category→node structure from taxonomy.yaml + merged pages. Taxonomy is non-binding: emergent nodes/categories (written but not in the taxonomy) are appended so nothing is ever hidden. Degrades gracefully to a parent-grouping if taxonomy.yaml is missing. */ function buildTaxonomy() { const tax = state.taxonomy && typeof state.taxonomy === 'object' ? state.taxonomy : {}; const pageByKey = new Map(); state.pages.forEach(p => { const parts = p.path.replace(/^topics\//, '').replace(/\.md$/, '').split('/'); p._cat = p.parent || parts[0] || 'other'; p._node = parts[parts.length - 1]; pageByKey.set(`${p._cat}/${p._node}`, p); }); const cats = []; const seenCat = new Set(); Object.keys(tax).forEach(cat => { seenCat.add(cat); const nodesObj = (tax[cat] && tax[cat].nodes) || {}; const nodes = Object.keys(nodesObj).map(slug => ({ slug, scope: nodesObj[slug], page: pageByKey.get(`${cat}/${slug}`) || null })); state.pages.filter(p => p._cat === cat && !nodesObj[p._node]).forEach(p => nodes.push({ slug: p._node, scope: '', page: p })); cats.push({ cat, description: (tax[cat] && tax[cat].description) || '', nodes }); }); const em = {}; state.pages.filter(p => !seenCat.has(p._cat)).forEach(p => { (em[p._cat] ||= []).push(p); }); Object.keys(em).sort().forEach(cat => cats.push({ cat, description: '', nodes: em[cat].map(p => ({ slug: p._node, scope: '', page: p })) })); cats.forEach(c => { c.written = c.nodes.filter(n => n.page).length; c.total = c.nodes.length; }); state.tax = cats; buildTopicTree(); } /* Arbitrary-depth topic tree from full paths (topics/a/b/.../node.md) overlaid with taxonomy categories (descriptions) + planned nodes. Powers the sidebar + Contents tree so a 3rd (or deeper) hierarchy level renders instead of being flattened away. */ function buildTopicTree() { const root = { seg: '', key: '', children: new Map() }; const child = (node, seg) => { if (!node.children.has(seg)) node.children.set(seg, { seg, key: node.key ? `${node.key}/${seg}` : seg, children: new Map() }); return node.children.get(seg); }; state.pageByKey = new Map(); // "cat/…/node" → page, for cross-ref linkifying state.pages.forEach(p => { const key = p.path.replace(/^topics\//, '').replace(/\.md$/, ''); state.pageByKey.set(key, p); const segs = key.split('/'); let node = root; segs.forEach(s => { node = child(node, s); }); node.page = p; node.title = p.title; node.article = true; // leaf }); const tax = state.taxonomy && typeof state.taxonomy === 'object' ? state.taxonomy : {}; root._order = Object.keys(tax); Object.keys(tax).forEach(cat => { const cn = child(root, cat); cn.description = (tax[cat] && tax[cat].description) || ''; const nodesObj = (tax[cat] && tax[cat].nodes) || {}; cn._order = Object.keys(nodesObj); Object.keys(nodesObj).forEach(slug => { const ex = cn.children.get(slug); if (!ex) cn.children.set(slug, { seg: slug, key: `${cat}/${slug}`, children: new Map(), planned: true, scope: nodesObj[slug] }); else if (ex.scope == null) ex.scope = nodesObj[slug]; }); }); state.topicTree = root; } // recursive rollup: words + inline refs + article/written/total counts under a node. // A node can be BOTH an article and a parent (topics/x.md + topics/x/sub.md) — count // its own article AND everything beneath it. function nodeStats(node) { const self = (node.article && node.page) ? { words: state.wordCount.get(node.page.path) || 0, refs: state.refCount.get(node.page.path) || 0, articles: 1, written: 1, total: 1 } : { words: 0, refs: 0, articles: 0, written: 0, total: 0 }; if (!node.children.size) return node.page ? self : { words: 0, refs: 0, articles: 0, written: 0, total: 1 }; // article leaf | planned leaf const acc = { ...self }; node.children.forEach(c => { const s = nodeStats(c); acc.words += s.words; acc.refs += s.refs; acc.articles += s.articles; acc.written += s.written; acc.total += s.total; }); return acc; } // children in taxonomy order where known, else alphabetical function treeChildren(node) { const kids = [...node.children.values()]; const ord = node._order || []; return kids.sort((a, b) => { const ia = ord.indexOf(a.seg), ib = ord.indexOf(b.seg); if (ia >= 0 || ib >= 0) return (ia < 0 ? 1e9 : ia) - (ib < 0 ? 1e9 : ib); return (a.title || catLabel(a.seg)).localeCompare(b.title || catLabel(b.seg)); }); } // is the active topic somewhere under this node? (to auto-open its branch) function subtreeHasActive(node) { const r = parseHash(); if (r.view !== 'topic' || !r.path) return false; const ap = r.path.replace(/^topics\//, '').replace(/\.md$/, ''); return ap === node.key || ap.startsWith(node.key + '/'); } // Home "Contents" tree — one internal node, recursively, with rollup counts. // Fully unfolded by default (all levels open); collapsing is opt-in and remembered. function homeTreeHtml(node, depth, ready) { const st = nodeStats(node); const open = !state.treeCollapsed.has(node.key); // front ToC fully unfolded by default (collapse is opt-in) const rollup = ready ? `${fmtNum(st.words)} words · ${fmtNum(st.refs)} refs` : ''; let inner = node.description ? `
${esc(node.description)}
` : ''; let planned = 0; treeChildren(node).forEach(k => { if (k.children.size) { inner += homeTreeHtml(k, depth + 1, ready); // nested sub-category (or article-with-children) } else if (k.page) { const pw = state.wordCount.get(k.page.path) || 0, pr = state.refCount.get(k.page.path) || 0; inner += `${esc(k.page.title || catLabel(k.seg))}${ready ? `${fmtNum(pw)} words · ${pr} refs` : ''}`; } else if (planned++ < MAP_PLANNED_CAP) { inner += `${esc(catDisplay(k.seg))}planned`; } }); if (planned > MAP_PLANNED_CAP) inner += `+${planned - MAP_PLANNED_CAP} more planned`; // A node that is both an article and a parent gets a linked header (to its own article) + a caret to expand. const key = esc(node.key); const dot = node.page ? '' : ''; // article-parents get the same green dot as leaf articles const name = node.page ? `${esc(node.page.title || catDisplay(node.seg))}` : `${esc(catDisplay(node.seg))}`; return `
${dot}${name} ${rollup}
${inner}
`; } const catLabel = (c) => prettyTitle((c || '').replace(/-/g, ' ')); // Sentence-cased category for headings, e.g. "reward-modeling" → "Reward modeling". const catDisplay = (c) => { const s = catLabel(c); return s.charAt(0).toUpperCase() + s.slice(1); }; // The /v1/wiki/pages list gives slug-derived titles ("Dpo And Offline Po"), which // mangle domain acronyms. Restore proper casing for known terms + lowercase the // little connector words. (Article headers use the real frontmatter title instead.) const ACRONYMS = { rl:'RL', llm:'LLM', llms:'LLMs', rlhf:'RLHF', rlaif:'RLAIF', rlvr:'RLVR', dpo:'DPO', ipo:'IPO', kto:'KTO', orpo:'ORPO', simpo:'SimPO', ppo:'PPO', grpo:'GRPO', trpo:'TRPO', gae:'GAE', kl:'KL', sft:'SFT', rm:'RM', orm:'ORM', prm:'PRM', mdp:'MDP', bon:'BoN', raft:'RAFT', rft:'RFT', ai:'AI', mcts:'MCTS', cot:'CoT', gpt:'GPT', api:'API', po:'PO', nlp:'NLP', qa:'QA', r1:'R1', o1:'o1', dapo:'DAPO', gspo:'GSPO', vapo:'VAPO', deepseek:'DeepSeek', openai:'OpenAI', mle:'MLE', bt:'BT', }; const SMALL_WORDS = new Set(['and','or','of','for','the','to','vs','in','on','a','an','with','from','as','at','via']); function prettyTitle(s) { return String(s == null ? '' : s).split(' ').map((w, i) => { const key = w.toLowerCase().replace(/[^a-z0-9]/g, ''); if (ACRONYMS[key]) return w.replace(/[A-Za-z0-9]+/, ACRONYMS[key]); if (i > 0 && SMALL_WORDS.has(key)) return w.toLowerCase(); return w; }).join(' '); } /* ============================================================ CITATION MAP — derive source↔article edges from article bodies. The API exposes no citation graph, but every article body carries [source:]; scanning the (bounded) set of articles once gives us load-bearingness (cite count), cited-by backlinks, and category links. Built lazily + cached so a reader who opens one article never pays for it. ============================================================ */ async function ensureCitemap() { if (state.citemapStarted) return; state.citemapStarted = true; const paths = state.pages.map(p => p.path); let i = 0; const worker = async () => { while (i < paths.length) { const path = paths[i++]; try { const content = await getPageContent(path); const ids = new Set(); const re = /\[source:([^\]\s]+)\]/g; let m, refs = 0; while ((m = re.exec(content))) { ids.add(m[1]); refs++; } // distinct → citedBy; occurrences → refCount ids.forEach(id => { if (!state.citedBy.has(id)) state.citedBy.set(id, new Set()); state.citedBy.get(id).add(path); }); state.refCount.set(path, refs); state.wordCount.set(path, countWords(content)); state.searchText.set(path, parseFrontmatter(content).body.toLowerCase()); // body only (frontmatter/open-questions excluded) so every hit maps to visible prose if (($('#navSearch').value || '').trim()) scheduleNavRefresh(); // live-fill an in-progress search } catch (e) { /* skip unreadable article */ } } }; // Low concurrency (3): the browser caps ~6 connections/host, and we must leave // room for user-initiated navigation fetches — background indexing must never // starve the foreground. await Promise.all(Array.from({ length: Math.min(3, paths.length) }, worker)); state.citedBy.forEach((set, id) => state.citeCount.set(id, set.size)); state.citemapReady = true; refreshCitationViews(); } // Prose word count — excludes frontmatter, code, math, citations, and markdown syntax. function countWords(md) { let t = String(md || '').replace(/^?---[\s\S]*?\n---\s*\n/, ''); t = t.replace(/```[\s\S]*?```/g, ' ').replace(/\$\$[\s\S]*?\$\$/g, ' ').replace(/\$[^\n$]*?\$/g, ' '); t = t.replace(/\[source:[^\]]*\]/g, ' ').replace(/[#>*_`|~]+/g, ' '); return t.split(/\s+/).filter(w => /[A-Za-z0-9]/.test(w)).length; } const fmtNum = (n) => (n || 0).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); // Re-render whatever's on screen that depends on citation data, now that it's ready. function refreshCitationViews() { const r = parseHash(); if (r.view === 'source' || r.view === 'home' || r.view === 'sources') softRefresh(); } // Year decoded for free from arxiv ids (YYMM.NNNNN); else from cited frontmatter is too costly, so null. function sourceYear(id) { const m = /^arxiv:(\d{2})(\d{2})\./.exec(id); if (!m) return null; const yy = +m[1]; return yy >= 91 ? 1900 + yy : 2000 + yy; // arXiv started 1991; everything else is 20xx } // Categories (taxonomy) that cite a source, via the topics that cite it. function sourceCategories(id) { const cats = new Set(); (state.citedBy.get(id) || new Set()).forEach(path => { const p = state.pages.find(pg => pg.path === path); if (p && p._cat) cats.add(p._cat); }); return [...cats]; } async function boot() { initTheme(); setupMarked(); // events — all optional-chained so a missing element can never abort boot and // blank the whole app (a stale index.html must degrade, not brick). $('#themeToggle')?.addEventListener('click', toggleTheme); $('#menuToggle')?.addEventListener('click', toggleNav); $('#navScrim')?.addEventListener('click', closeNav); $('#brandHome')?.addEventListener('click', () => go('#/')); let st; // One search box: filters topic titles + bodies, and live-highlights the open article. $('#navSearch')?.addEventListener('input', () => { ensureCitemap(); // kick off body indexing on first keystroke so content matches fill in fast syncSearchBadge(); clearTimeout(st); st = setTimeout(() => { renderNav(); refreshDocHighlights($('#navSearch').value); }, 120); }); window.addEventListener('hashchange', route); // The ⌘K badge doubles as a ✕ clear button once a query is typed. $('#paletteOpen')?.addEventListener('click', () => { $('#navSearch')?.value ? clearNavSearch() : focusNavSearch(); }); // ⌘K (and "/") focus the top-left search — one box that filters titles + bodies. window.addEventListener('keydown', (e) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); focusNavSearch(); return; } if (e.key === 'Escape' && document.activeElement === $('#navSearch') && $('#navSearch')?.value) { clearNavSearch(); return; } const typing = /^(input|textarea)$/i.test(document.activeElement?.tagName || ''); if (e.key === '/' && !typing) { e.preventDefault(); focusNavSearch(); } }); initHovercards(); // Sources/meta are independent of pages+taxonomy — start them NOW (in parallel with // loadCore) so the source list + citation resolution are ready ASAP, not gated on // the critical render path. (softRefresh is a no-op until the first render lands.) loadSources().catch(e => console.warn('sources', e)); loadMeta().catch(e => console.warn('meta', e)); // Critical path: render as soon as topics + taxonomy are in (sub-second). try { await loadCore(); } catch (e) { showError('Could not reach the wiki API. ' + (e.message || '')); return; } updateCounts(); renderNav(); route(); state.firstRenderDone = true; // Citation map a bit after first paint so its bulk fetches don't contend with the // current view's content fetch for the per-host connection budget. setTimeout(() => ensureCitemap(), 1500); } // marked & friends are loaded with `defer`; run after DOM + scripts are ready. if (document.readyState === 'loading') window.addEventListener('DOMContentLoaded', boot); else boot();