Note
Version 1.2 is here. Grab the complete 1.2 package from releases.
This folder is a standalone, static documentation viewer: Markdown sources, a generated manifest, and a single-page app that renders docs in the browser with marked and DOMPurify. You can copy this tree into its own repository and host it on any static file host.
There is no server-side rendering. You do need to regenerate manifest.json whenever you add, remove, or rename .md files under content/ (or change manifest-driven fields such as category order in category-order.json), so the sidebar stays in sync.
Check out the base project live over at my site, retrograde.org.uk.
Or, if you fancy seeing it in action, this project has already been used in the wild by OfficiallySp for his API documentation for ChatStats.live - If you would like to see this projects capabilities in a production setting, check out ChatStats.live and while you're at it, check out their awesome project too!
- Requirements
- Directory layout
- Quick start
- How it works
- Front matter reference
- The manifest
- Authoring Markdown
- Routing and URLs
- UI behavior
- Announcement bar
- Local development
- Deployment
- Troubleshooting
- File reference
| Need | Why |
|---|---|
| HTTP server | Browsers block fetch() for manifest.json and .md files from file://. Use any static host or a one-line local server. |
| Node.js (optional but recommended) | To run scripts/build-manifest.mjs and refresh manifest.json. |
| Network (first visit) | main/index.html loads marked and DOMPurify from jsDelivr CDN. |
Everything the viewer needs lives inside this folder (no dependency on paths outside it):
Documentation/
├── README.md ← this file
├── index.html ← optional landing page (site home)
├── category-order.json ← optional; ordered list of sidebar category names
├── manifest.json ← generated; lists all docs for the sidebar
├── doc-app.js ← client app: manifest, sidebar, MD render, nav
├── src/
│ ├── images/ ← e.g. R.png for favicon and header logos
│ ├── announcement-banner.js ← dismiss + versioned localStorage for the site banner
│ └── styles.css ← shared styles for `index.html` and `main/index.html`
├── content/ ← all documentation Markdown
│ ├── core/
│ ├── guides/
│ └── experiments/
├── main/
│ └── index.html ← documentation viewer entry
└── scripts/
└── build-manifest.mjs ← scans content/ and writes manifest.json
Paths in front matter (e.g. prev, next) are relative to this Documentation root, same as entries in manifest.json (for example content/guides/writing-docs.md).
-
Add or edit Markdown under
content/using the front matter format. -
Optional: edit
category-order.jsonto control the order of categories in the sidebar (see The manifest). Skip this file if alphabetical category order is fine. -
Regenerate the manifest from inside this folder:
cd Documentation node scripts/build-manifest.mjsOr from a parent repo path:
node path/to/Documentation/scripts/build-manifest.mjs
-
Commit
manifest.json(andcategory-order.jsonif you use it) if you use Git. -
Open the viewer over HTTP. If the server root is the
Documentationfolder:http://localhost:PORT/main/index.htmlIf the server root is a parent directory, include the path segment (for example
http://localhost:PORT/Documentation/main/index.html).
-
main/index.htmlloadsdoc-app.jswithdata-doc-base="../"so requests resolve tomanifest.jsonandcontent/...next tomain/. -
doc-app.jsfetchesmanifest.json, groups documents by category, orders categories usingcategoryOrderfrom the manifest when present (otherwise A–Z), sorts within each group by order then title, and renders the left sidebar. -
Navigation uses the URL hash:
#/content/core/getting-started.md. The default document is the first entry in reading order if the hash is missing or invalid. -
For the active page, the app fetches the raw
.mdfile, parses the HTML-comment front matter, strips it from the body, runs the body through marked (GFM), then DOMPurify, and injects the result into the article panel. -
Authors, tags, and previous/next controls are driven by front matter on the current page.
Assets: main/index.html uses ../src/images/ for icons and the header logo. Keep those images in src/images/ when you copy this repo.
Place a single HTML comment at the very top of each .md file. Inside it, put a YAML block between --- lines:
<!--
---
title: Page title
category: Core
order: 10
tags: ["tag-one", "tag-two"]
authors: ["Author Name"]
author: Single Author
last_edited: YYYY-MM-DD
prev: content/<folder>/<title>.md
next: content/<folder>/<title>.md
prev_label: Optional override
next_label: Optional override
---
-->
# Page title
Body starts here.| Field | Required | Description |
|---|---|---|
| title | Recommended | Page title; shown as the main <h1> and in the sidebar. |
| category | Recommended | Groups the page in the sidebar (e.g. Core, Guides). Defaults to General if omitted. |
| order | Recommended | Number used to sort pages within a category (lower first). Non-numeric values fall back to a default ordering. |
| tags | Optional | JSON-like array string, e.g. ["docs", "setup"]. Shown as pills below authors. Included in manifest.json. |
| authors | Optional | Array of author names, e.g. ["Ada", "Bob"]. Shown above tags. Included in manifest.json. |
| author | Optional | Single author string; shorthand for one author. You can use either authors or author, not both for the same intent. |
| last_edited | Optional | ISO date (YYYY-MM-DD) or free text; shown at the top right next to the breadcrumb in the doc viewer (not in manifest.json). |
These are optional and per page. Omit a key when there is no sensible link (e.g. do not force “next” into an unrelated section).
| Field | Description |
|---|---|
| prev | Path to the previous page, e.g. content/core/getting-started.md. |
| next | Path to the next page. |
| prev_label | Optional subtitle on the Previous button (defaults to the linked page’s title from the manifest). |
| next_label | Optional subtitle on the Next button. |
To explicitly disable a side in tooling or docs, you may use the value none (case-insensitive); it is treated as “no link.”
If both prev and next are absent (or effectively empty), the footer nav is hidden—no placeholder buttons.
- One line per key in the YAML block (no multi-line YAML values).
- Line endings may be LF or CRLF (Windows); both are split correctly when parsing keys.
- Keys match
^\w+(letters, numbers, underscore). - Empty values after
:are skipped. - Arrays are written on one line as
[...]with JSON-style strings preferred.
manifest.json is generated; do not hand-edit it except in emergencies.
Each document entry typically includes:
path— path relative to the Documentation roottitle,category,ordertags— arrayauthors— array (normalized fromauthors/authorin front matter)
The client uses the manifest for the sidebar and for default titles on prev/next links. It does not need to list prev/next; those are read from each file when it loads.
Sidebar sections are ordered by category. To control that order explicitly, add category-order.json at the same level as manifest.json: a JSON array of strings matching your front-matter category values, in the order you want (example: ["Core", "Guides", "Breakdown"]).
scripts/build-manifest.mjsreads this file when it exists and writes a top-levelcategoryOrderarray intomanifest.json.doc-app.jssorts categories usingcategoryOrder; any category not named in the list appears after all listed ones, sorted A–Z.- If
category-order.jsonis absent (or invalid), categories sort A–Z andcategoryOrderis omitted from the manifest.
After changing category-order.json, run the manifest script again and deploy the new manifest.json.
-
Use normal Markdown; GFM features (tables, task lists where supported) follow marked options (
gfm: true). -
Internal links to other doc pages can use hash URLs, for example:
See [Writing documentation](#/content/guides/writing-docs.md).
-
Callouts — highlighted asides with a colored rail and icon. Start a blockquote with a marker line, then continue the body with
>on each line (the Markdown parser may merge those into one paragraph; that is fine):> [!NOTE] > Highlights information readers should notice even when skimming.
Supported markers (case-insensitive):
[!NOTE],[!TIP],[!IMPORTANT],[!WARNING],[!CAUTION]. The marker must be the first line of the blockquote (only the marker on that line). Lists and other block content can follow on further>lines. Any other blockquote, or an unknown marker such as[!CUSTOM], stays a normal quote so the raw marker remains visible.For examples and edge cases, see the Callouts section in
content/guides/writing-docs.md. -
After adding or renaming files, always run the manifest script and commit
manifest.json.
- Hash format:
#/content/path/to/file.md(leading#/is normalized by the app). - Default page when the hash is missing or unknown: first document in reading order — sort by category (using
categoryOrderfrom the manifest when present, otherwise A–Z), then order, then title.
- Lists categories and pages from
manifest.json. Category order followscategoryOrderin the manifest when the build included it; see Category order under The manifest. - On wide viewports the sidebar is a scrollable column with a max height so it does not stretch to match the full article height.
- Desktop (≥881px): Hide sidebar / Show sidebar toggles collapse; state is stored in
localStorageunderr2sp-doc-sidebar-collapsed(1= collapsed). - Mobile (≤880px): Menu opens a drawer; backdrop tap or Escape closes it. Choosing a sidebar link closes the drawer.
- Breadcrumb: category · title (from front matter).
- Authors (if any), then tags (if any).
- Previous / Next (if
prevand/ornextare set in front matter). - Body: rendered Markdown.
- Collapsed sidebar uses
aria-hiddenon the aside where appropriate; buttons exposearia-expanded/aria-controlswhere implemented.
The landing page (index.html) and the docs shell (main/index.html) share a thin announcement strip above the main content. It includes a short message and a dismiss control (×).
Behavior
src/announcement-banner.jsruns on both pages. When a visitor dismisses the bar, the choice is stored inlocalStorageunder a key scoped bydata-announcement-idon the banner (for exampledocutron.announcement.dismissedVersion.home), together with the current version string (not the message text).data-announcement-idmust differ per page or shell so each announcement is dismissed independently; two pages can both use version"1"without sharing dismissal.- On later visits, if the stored version matches
data-announcement-versionon that banner, the bar stays hidden. IflocalStorageis unavailable (or throws), the bar still works; dismissal simply is not remembered for that session or browser.
Changing the message and showing the bar again
- Edit the copy inside
<section id="site-announcement">on the relevantindex.html(or other shell page). - Increment
data-announcement-versionon that section only (for example from"1"to"2"). Anyone who had dismissed the previous version for that id will see the new announcement; the new dismissal is stored under that id and version.
Serve the Documentation directory (or its parent, adjusting URLs):
cd Documentation
python -m http.server 8080Then open:
http://127.0.0.1:8080/main/index.html
Regenerate the manifest after content changes:
node scripts/build-manifest.mjs- Upload or deploy this entire folder (or the whole repo if it only contains this tool).
- Ensure
main/index.html,doc-app.js,manifest.json,content/**/*.md, andsrc/(images,announcement-banner.js, styles, etc.) are all present at the paths expected by the HTML. - HTTPS or HTTP consistently; CDN scripts (jsDelivr) use HTTPS.
- Re-run the manifest script in CI before deploy if Markdown changes.
cPanel / static hosts: place the contents under public_html or a subdomain folder; the URL to the viewer will be https://yourdomain.com/main/index.html (plus any subdirectory prefix).
| Symptom | Likely cause | What to do |
|---|---|---|
Blank error about manifest.json |
File missing or wrong BASE path |
Run build-manifest.mjs; check server root and data-doc-base on the script tag. |
| New page missing from sidebar | Manifest stale | Regenerate and deploy manifest.json. |
fetch failed / CORS |
Opening index.html via file:// |
Serve over HTTP. |
| Prev/next wrong or missing | Front matter on that page | Edit prev / next (and optional labels) in the .md file. |
| Sidebar always collapsed on desktop | Stored preference | Clear localStorage key r2sp-doc-sidebar-collapsed or click Show sidebar. |
| Announcement bar will not stay dismissed | Private mode / blocked storage | Dismissal needs localStorage; if it is blocked, the bar returns on each load. |
| Old visitors do not see a new announcement | Version not bumped for that banner | Increase data-announcement-version on the <section id="site-announcement"> where you changed the copy (per data-announcement-id). |
| Broken images in header | Missing src/images/ |
Add R.png (or your assets) under src/images/ and keep paths in main/index.html in sync. |
| File | Role |
|---|---|
index.html |
Optional landing page; links can point to main/index.html. |
main/index.html |
Documentation viewer shell: header, layout, article, scripts (marked, DOMPurify, doc-app.js). |
src/styles.css |
Layout, theme, sidebar, article, prev/next, responsive rules (loaded by index.html and main/index.html). |
doc-app.js |
Fetches manifest and Markdown; parses front matter; renders UI; hash routing; sidebar mobile + desktop behavior. |
scripts/build-manifest.mjs |
Walks content/**/*.md, parses front matter; merges optional category-order.json; writes manifest.json. |
category-order.json |
Optional ordered list of category names; drives categoryOrder in the manifest. |
manifest.json |
Index of docs for the sidebar and metadata snapshot. |
content/**/*.md |
Source documentation. |
src/announcement-banner.js |
Versioned dismiss for #site-announcement; loaded from index.html and main/index.html. |
src/images/ |
Static images referenced by index.html and main/index.html. |
Markdown rendering relies on marked and DOMPurify (see main/index.html for versions and URLs). Optional branding and outbound links in the HTML are yours to customize for your project.
If you're still here after reading all of this and you're interested in my solo project, thank you for considering it. And thank you for supporting me if you do end up downloading this. Made with 💖 by Retrograde