-
Notifications
You must be signed in to change notification settings - Fork 1
html rendering
Good, I have enough to write the page.
- src/repodocs/render.py
- tests/test_fix_render_links.py
render.py turns the per-page Markdown files produced by generate into a single self-contained wiki.html file: a static-site-like viewer with a sidebar nav, search filter, table of contents, GitHub citation links, and Mermaid/highlight.js rendering, all driven by a client-side JavaScript router that reads the page content out of an embedded JSON blob rather than making network requests.
Sources: src/repodocs/render.py:L413-L461
build_html(repo, out, vendor=False) is the entry point. It:
- Globs
*.mdfiles inout(excludingindex.md) and fails viadie()if none exist. - Loads
out/plan.json, if present, to recover each page'stitleand the intended pageorder(falling back to the slug and alphabetical order otherwise). - Derives a
breadcrumb(repo slug or directory name) and an optionalghrootGitHub URL viagithub_base(repo). - Decides whether citation links can be rewritten to absolute
blob/<sha>GitHub URLs (cite_base) or must stay relative, based oncitations_safe()overgit status --porcelainandgit branch -r --contains HEAD— dirty trees or unpushed HEADs disable absolute citation rewriting and a warning is printed to stderr. - Reads each Markdown file, extracts its title with
md_title()(falling back toplan.json's title or the slug), and rewrites citation links withrewrite_citation_links(text, cite_base). - Orders and groups pages with
group_pages(), producinggroups(nav sections) and a flatorderlist used for prev/next pagination. - If
vendor=True, downloads the pinned CDN assets intoout/assets/viavendor_assets()for fully offline use. - Infers UI language labels from the output directory name via
lang_labels(out.name)(e.g., anoutdir namedptyields Portuguese labels). - Renders the final document with
render_html(...)and writes it toout/wiki.html.
Sources: src/repodocs/render.py:L413-L461

Sources: src/repodocs/render.py:L413-L461
md_title(text) scans a Markdown page line by line for the first ^#\s+(.+) match and returns the captured heading text, or None if the page has no top-level heading. build_html prefers plan.json's recorded title, then falls back to md_title, then to the slug itself.
Sources: src/repodocs/render.py:L464-L469
group_pages(slugs) buckets slugs into four fixed, deterministically ordered groups — Overview, Features, Reference, Development — while preserving the plan's relative order within each bucket, and omits empty groups from the result:
| Group | Membership rule |
|---|---|
| Overview | slug in {"overview", "installation", "limitations", "changelog"}
|
| Development | slug in {"development", "testing", "contributing", "security", "dev-setup"}
|
| Reference | slug equals "architecture", or contains "architecture" or "interop"
|
| Features | everything else |
Sources: src/repodocs/render.py:L73-L88
render.py hard-codes pinned, exact-semver CDN URLs in CDN_ASSETS for marked, mermaid, highlight.min.js (hljs), its stylesheet (hljscss), and dompurify, each paired with a sha384 Subresource Integrity hash in SRI. When vendor=True, vendor_assets(out) downloads each of VENDOR_FILES into out/assets/ and writes THIRD-PARTY-NOTICES.txt (attribution text for marked, Mermaid, highlight.js, and DOMPurify), and render_html swaps in the local VENDOR_ASSETS paths instead of the CDN URLs. _asset_sri_attr(vendor, key) returns an empty string for vendored assets — their local bytes don't match the pinned CDN hash, so tagging them with integrity would make the browser refuse to load them — and otherwise returns the integrity="sha384-..." crossorigin="anonymous" attribute pair.
Sources: src/repodocs/render.py:L15-L50, src/repodocs/render.py:L188-L197, src/repodocs/render.py:L372-L379
Tests confirm every CDN_ASSETS URL is pinned to an exact major.minor.patch version (no floating @N/ majors) and carries a sha384- SRI hash, that CDN-mode HTML includes integrity/crossorigin on each <script>/<link> tag, and that vendor-mode HTML omits integrity entirely for those same assets.
Sources: tests/test_fix_render_links.py:L43-L67
HTML_TEMPLATE is a single self-contained HTML document (inline <style> and <script>) with placeholder tokens (__TITLE__, __BREADCRUMB__, __GHLINK__, __MARKED__, __MERMAID__, __HLJS__, __HLJSCSS__, __DOMPURIFY__, their *_SRI__ counterparts, __REPO__, __LABELS__, __GROUPS__, __ORDER__, and __PAGES__). render_html(breadcrumb, ghroot, data, groups, order, vendor, labels) substitutes each token:
-
breadcrumband the GitHub link text/href are HTML-escaped withhtml.escape(..., quote=True)since they may originate from an untrusted repo directory name or remote slug. -
__REPO__is embedded as a JSON string with</escaped to<\/to prevent breaking out of the inline<script>block. -
__PAGES__(the full{slug: {title, md}}map) is substituted last, with the same</escaping, so that embedded page Markdown cannot clobber earlier token replacements.
Sources: src/repodocs/render.py:L200-L269, src/repodocs/render.py:L382-L410
Regression tests assert a malicious breadcrumb containing </title><script> is fully escaped inside <title> and cannot break the shared <script> block via the REPO assignment, and that a malicious ghroot value is HTML-escaped as an attribute rather than injecting a live <script> element.
Sources: tests/test_fix_render_links.py:L15-L40
The embedded <script> in HTML_TEMPLATE implements a hash-router single-page app over the PAGES/GROUPS/ORDER/REPO/LABELS globals injected by render_html:
-
Navigation is built from
GROUPS, each rendered as a.navgroupwith a.navheadlabel (translated viaLABELS.groups) and.navlinkanchors per slug; a#filtertext input hides/shows nav links and their group headers by substring match. -
show(slug)looks upPAGES[slug], and ifDOMPurifyfailed to load, refuses to render page content at all (fail-closed) rather than risk unsanitized HTML. Otherwise it rendersp.mdthroughmarked.parseand sanitizes the result withDOMPurify.sanitize(..., { ADD_ATTR: ["target"] })before inserting it into#content. - Fenced
```mermaidcode blocks (rendered bymarkedas<code class="language-mermaid">) are converted to<div class="mermaid">elements and rendered viamermaid.run(...), initialized withsecurityLevel: "strict". -
collapseSources(content)wraps the first<h2>(matched structurally, not by heading text, since wording varies by language/model) plus its following<ul>file-list sibling into a collapsible<details>element — this is the page's "Relevant source files" section. -
buildToc(content)builds the right-hand table of contents from all<h2>/<h3>headings, assigning each anid="sec-N"and a smooth-scroll anchor link. -
pageFooter(content, slug)appends previous/next page navigation usingORDER, with its HTML also passed throughDOMPurify.sanitize. - Highlighting is applied per code block via
hljs.highlightElementwhenwindow.hljsis present. - The router reads
location.hashon load and onhashchangeto callshow(), defaulting toORDER[0]if the hash doesn't match a known slug.
Sources: src/repodocs/render.py:L271-L366
Page Markdown produced by generate contains relative citation links like <a href="https://github.com/aryrabelo/repodocs/blob/51ba8b18a6abbf0ed1789af067cb1c932993f747/path#L1-L2" target="_blank" rel="noopener">label</a>; before embedding, build_html calls rewrite_citation_links(text, cite_base) (from gitlinks.py) to turn these into absolute GitHub blob URLs when it is safe to do so. Safety is determined by citations_safe(), which treats a working tree as unsafe (falling back to relative links) if git status --porcelain shows tracked changes, or shows untracked files outside the out directory — since an untracked source file could be exactly what a citation points at and would not exist in the pushed commit — and also requires that HEAD is contained in a remote branch (via git branch -r --contains HEAD).
Sources: src/repodocs/render.py:L427-L442
Regression tests cover this from the gitlinks.py side: rewrite_citation_links HTML-escapes a malicious base URL (e.g. one containing " onmouseover="alert(1)) so it cannot inject a live attribute into the rendered <a href>; citations_safe rejects a dirty tree when an untracked file lies outside out_rel, allows untracked entries that are themselves under out_rel, and defaults to "dirty" when out_rel is not supplied at all.
- Home
- Installation & Setup
- Architecture
- CLI Reference
- LLM Backend Selection
- Repository Scanning
- Wiki Page Planning
- Page Generation
- Source Citations
- Translation
- HTML Rendering
- Diagram Rendering
- Diagram Poster Tool
- Git Remote Link Resolution
- Publishing
- GitHub Wiki Integration
- Shared Utilities
- Environment Configuration
- Security & Trust Boundaries
- Limitations & Non-goals
- Testing
- Development
- Contributing
- Upgrading
- Changelog