/* ============================================================ 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) palItems: [], // command-palette index palFiltered: [], palSel: 0, pageContent: new Map(), // path -> article markdown (cached) 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' 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) }; /* ── 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; } 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.innerHTML = `
${esc(info.title || id)}
` + (authors ? `
${esc(authors)}
` : '') + (line ? `
${line}
` : '') + `
${esc(id)}${info.type ? ` · ${esc(info.type)}` : ''}
`; 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() { const sel = '[data-src]'; document.addEventListener('mouseover', (e) => { const a = e.target.closest(sel); if (!a) return; const id = a.dataset.src; if (!id) return; // Suppress the browser's native title tooltip (it would pop over the hovercard // after its ~1.5s delay); stash it to restore on mouse-out. if (a.hasAttribute('title')) { a.dataset.title = a.getAttribute('title'); a.removeAttribute('title'); } _hoverId = id; clearTimeout(_hoverTimer); _hoverTimer = setTimeout(() => showHovercard(a, id), 200); }); document.addEventListener('mouseout', (e) => { const a = e.target.closest(sel); 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); }, }], }); // Cross-references between topics are authored as bare `category/node` code // spans (e.g. `preference-data/ai-feedback-data`) rather than markdown links. // Turn each into a live hop to that topic — but only when the page exists, so // genuine code spans (`preference/DPO`, planned-but-unwritten nodes) stay plain. // (Markdown-link cross-refs are handled separately by rewriteInternalLinks; // this href is already a `#/` route, so that pass leaves it untouched.) marked.use({ renderer: { // marked@13 hands codespan the already-HTML-escaped code text as a plain // string (the {text} token arg is a marked-v16 API) — so read it directly // and don't re-escape, or every cross-ref silently falls through to plain. codespan(code) { const m = /^([a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*)$/.exec(code); if (m) { const path = `topics/${m[1]}.md`; if (state.pages.some(p => p.path === path)) { return `${code}`; } } return false; // fall back to marked's default code-span rendering }, }, }); 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(body); } catch (e) { console.error('marked failed', e); return `
${esc(body)}
`; } } /* 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) {} }); rewriteInternalLinks(root, currentPath); // markdown links to topics/sources → in-app routes 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'; }); return toc; } // Walk visible text nodes under root, skipping code/math/links/headings/abbr. function walkTextNodes(root, fn) { const 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); } }); } // 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', }; 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.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; // "~" const t = arg.indexOf('~'); if (t >= 0) { section = arg.slice(t + 1); arg = arg.slice(0, t); } arg = decodeURIComponent(arg); if (kind === 'topic') return { view: 'topic', path: arg, section }; if (kind === 'source') return { view: 'source', id: arg, section }; 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); return; } highlightActive(); 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); } 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' }); } // 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 || ''; } 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.

`; if (!state.tax.length) { html += ``; } else { const ready = state.citemapReady; // rollups per category + root let rootW = 0, rootR = 0, rootA = 0; const cats = state.tax.map(c => { const written = c.nodes.filter(n => n.page); let w = 0, r = 0; written.forEach(n => { w += state.wordCount.get(n.page.path) || 0; r += state.refCount.get(n.page.path) || 0; }); rootW += w; rootR += r; rootA += written.length; return { c, written, w, r }; }); const rollup = (w, r) => ready ? `${fmtNum(w)} words · ${fmtNum(r)} refs` : ''; html += `
`; html += `
${ready ? `${fmtNum(rootW)} words · ${fmtNum(rootR)} references · ${rootA} articles across ${state.tax.length} categories` : ` measuring depth across ${state.pages.length} articles…`}
`; cats.forEach(({ c, written, w, r }) => { const collapsed = state.treeCollapsed.has(c.cat); const planned = c.nodes.filter(n => !n.page); const shownPlanned = planned.slice(0, MAP_PLANNED_CAP); html += `
${c.description ? `
${esc(c.description)}
` : ''} ${written.map(n => { const pw = state.wordCount.get(n.page.path) || 0, pr = state.refCount.get(n.page.path) || 0; return ` ${esc(n.page.title || n.slug)} ${ready ? `${fmtNum(pw)} words · ${pr} refs` : ''}`; }).join('')} ${shownPlanned.map(n => ` ${esc(catDisplay(n.slug))}planned`).join('')} ${planned.length > shownPlanned.length ? `+${planned.length - shownPlanned.length} more planned` : ''}
`; }); 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); 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.id)}${esc(s.title || '')} ${n ? `cited ${n}×` : ''}
`; }); 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 head = document.querySelector(`.tree-cat-head[data-tcat="${(window.CSS && CSS.escape) ? CSS.escape(cat) : cat}"]`); if (head) { head.classList.toggle('collapsed'); head.nextElementSibling?.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); $('#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(true); setView(`
Wiki/ ${esc(catDisplay(cat))}
Topic Article

${esc(title)}

${maturityBadge(fm.maturity)} ${fm.sources ? `${fm.sources.length} source${fm.sources.length === 1 ? '' : 's'} cited` : ''}
${renderBody(body)}
${footerHTML()} `); wireShare(); const toc = enhanceProse($('#prose'), path); bindInternalLinks($('#prose')); // Aside: cited sources, open questions, TOC, links let aside = ''; if (fm.sources?.length) { aside += `
Cited sources
` + fm.sources.map(id => sourceChip(id)).join('') + `
`; } if (fm.open_questions?.length) { aside += `
Open questions
    ` + fm.open_questions.map(q => `
  • ${renderInline(q)}
  • `).join('') + `
`; } aside += tocPanel(toc); aside += `
This page
`; setAside(aside); initScrollSpy(toc); } /* ── 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(); } /* Topics: taxonomy-ordered, collapsible categories. Only categories that contain a written article appear; counts show written/total so gaps show. */ function renderTopicsNav(list, q) { const active = parseHash(); const cats = state.tax.filter(c => c.written > 0); if (!cats.length) { list.append(el('div', 'nav-empty', state.pages.length ? '' : 'No topics yet.')); return; } let shownAny = false; cats.forEach(c => { const written = c.nodes.filter(n => n.page); const matches = q ? written.filter(n => matchPage(n.page, q)) : written; if (!matches.length) return; shownAny = true; const hasActive = matches.some(n => n.page.path === active.path); const collapsed = !q && state.collapsed.has(c.cat) && !hasActive; const wrap = el('div', 'nav-cat'); const head = el('button', `nav-cat-head${collapsed ? ' collapsed' : ''}`, `${esc(catDisplay(c.cat))}` + `${c.written}/${c.total}`); head.addEventListener('click', () => toggleCat(c.cat)); wrap.append(head); const body = el('div', `nav-cat-body${collapsed ? ' collapsed' : ''}`); matches.forEach(n => { const it = el('div', 'nav-item'); it.dataset.path = n.page.path; it.innerHTML = `${esc(n.page.title || n.slug)}`; it.addEventListener('click', () => go(`#/topic/${encodeURIComponent(n.page.path)}`)); body.append(it); }); wrap.append(body); list.append(wrap); }); if (!shownAny) list.append(el('div', 'nav-empty', 'No matching topics.')); } /* ── 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); } const q = (_srcQuery || '').toLowerCase().trim(); let items = state.sources.filter(s => (!state.srcNs || s.id.startsWith(state.srcNs + ':')) && (!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); 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(' · '); row.innerHTML = `${esc(s.id)}${esc(s.title || '')}${esc(meta)}`; 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; } const matchPage = (p, q) => (p.title || '').toLowerCase().includes(q) || p.path.toLowerCase().includes(q); 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').forEach(it => { it.classList.toggle('active', r.view === 'topic' && it.dataset.path === r.path); }); $('#lnkSources')?.classList.toggle('active', r.view === 'sources'); } /* ============================================================ COMMAND PALETTE — universal jump (⌘K / Ctrl+K / "/") Filters an in-memory index, so it stays instant at thousands of items. ============================================================ */ // `text` and `q` must already be lowercased (callers pre-lower for speed). function fuzzyScore(q, text) { const idx = text.indexOf(q); if (idx === 0) return 1000; // prefix match — best if (idx > 0) return 700 - Math.min(idx, 300); // subsequence fallback — start at the first occurrence of q[0]; bail cheaply if absent. let start = text.indexOf(q[0]); if (start < 0) return -1; let qi = 1, gaps = 0, last = start; for (let ti = start + 1; ti < text.length && qi < q.length; ti++) { if (text[ti] === q[qi]) { gaps += ti - last - 1; last = ti; qi++; } } return qi === q.length ? 300 - Math.min(gaps, 200) - Math.min(start, 50) : -1; } function paletteResults(raw) { const q = raw.toLowerCase().trim(); if (!q) return state.palItems.slice(0, 40); const scored = []; for (const it of state.palItems) { const s = Math.max(fuzzyScore(q, it._l), fuzzyScore(q, it._s) - 60); if (s > -1) scored.push({ it, s }); } scored.sort((a, b) => b.s - a.s || a.it.label.localeCompare(b.it.label)); return scored.slice(0, 40).map(x => x.it); } 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)); } function renderPalette() { const q = $('#palInput').value; state.palFiltered = paletteResults(q); if (state.palSel >= state.palFiltered.length) state.palSel = 0; const box = $('#palResults'); if (!state.palFiltered.length) { box.innerHTML = `
No matches for “${esc(q)}”.
`; return; } box.innerHTML = state.palFiltered.map((it, i) => `
${it.kind} ${highlightMatch(it.label, q)}${esc(it.sub)}
`).join(''); $$('#palResults .pal-item').forEach(n => { n.addEventListener('mousemove', () => { if (state.palSel !== +n.dataset.i) { state.palSel = +n.dataset.i; markPalSel(); } }); n.addEventListener('click', () => choosePal(+n.dataset.i)); }); } function markPalSel() { $$('#palResults .pal-item').forEach((n, i) => n.classList.toggle('sel', i === state.palSel)); } function scrollPalSel() { $$('#palResults .pal-item')[state.palSel]?.scrollIntoView({ block: 'nearest' }); } function choosePal(i) { const it = state.palFiltered[i]; if (it) { closePalette(); go(it.hash); } } function openPalette() { const sc = $('#palScrim'); sc.hidden = false; const inp = $('#palInput'); inp.value = ''; state.palSel = 0; renderPalette(); inp.focus(); } function closePalette() { $('#palScrim').hidden = true; } function palKeydown(e) { if (e.key === 'ArrowDown') { e.preventDefault(); state.palSel = Math.min(state.palSel + 1, state.palFiltered.length - 1); markPalSel(); scrollPalSel(); } else if (e.key === 'ArrowUp') { e.preventDefault(); state.palSel = Math.max(state.palSel - 1, 0); markPalSel(); scrollPalSel(); } else if (e.key === 'Enter') { e.preventDefault(); choosePal(state.palSel); } else if (e.key === 'Escape') { e.preventDefault(); closePalette(); } } /* ── 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(); buildPaletteIndex(); } 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() { buildPaletteIndex(); 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(() => {}), ]); updateCounts(); if (parseHash().view === 'home') softRefresh(); } /* 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; } function buildPaletteIndex() { const mk = (kind, label, sub, hash) => ({ kind, label, sub, hash, _l: label.toLowerCase(), _s: sub.toLowerCase() }); state.palItems = [ ...state.pages.map(p => mk('topic', p.title || p.path, catLabel(p._cat || p.parent || ''), `#/topic/${encodeURIComponent(p.path)}`)), ...state.sources.map(s => mk('source', s.title || s.id, s.id, `#/source/${encodeURIComponent(s.id)}`)), ]; } 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)); } 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 $('#themeToggle').addEventListener('click', toggleTheme); $('#menuToggle').addEventListener('click', toggleNav); $('#navScrim').addEventListener('click', closeNav); $('#brandHome').addEventListener('click', () => go('#/')); let st; $('#navSearch').addEventListener('input', () => { clearTimeout(st); st = setTimeout(renderNav, 120); }); window.addEventListener('hashchange', route); // command palette $('#paletteOpen').addEventListener('click', openPalette); $('#palInput').addEventListener('input', renderPalette); $('#palInput').addEventListener('keydown', palKeydown); $('#palScrim').addEventListener('click', (e) => { if (e.target === $('#palScrim')) closePalette(); }); window.addEventListener('keydown', (e) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); $('#palScrim').hidden ? openPalette() : closePalette(); return; } const typing = /^(input|textarea)$/i.test(document.activeElement?.tagName || ''); if (e.key === '/' && !typing && $('#palScrim').hidden) { e.preventDefault(); openPalette(); } }); 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();