Thanks for looking. This is a static site generator with a thin CSS compile step (Tailwind CLI) and otherwise Node 20+ ES modules. The point of this page is to get you from "I want to change that" to the file that says it.
If you only want to add documentation to a DayZ declaration, you do not need any of this — see Community notes, which is one JSON file.
npm install # Tailwind CLI (devDependency)
npm run fetch # clone/update the upstream sources, detect builds
npm run parse # parse them into data/model-<build>.json (cached by commit sha)
npm run dev # http://localhost:3000 — renders on demand, reloads on save
npm testnpm run dev is the inner loop, and it never touches dist/. It loads the
newest build's model once and renders whichever page you open, in a few
milliseconds each; older builds render the same way at /v/<label>/. Client
JavaScript is served straight out of site/; CSS is compiled by Tailwind into
.cache/styles.css and served as /assets/styles.css. The dev script runs
css:watch and the Node server together so a save to styles or a renderer
reloads the browser.
npm run generate runs css:build then writes the whole site into dist/,
which is what Netlify runs. You rarely need it while working;
npm run generate:latest does only the newest build if you do.
flowchart LR
fetch["src/fetch.js<br/>clone upstream"] --> parse["src/parse-all.js<br/>src/parser/"]
parse --> dataDir[("data/<br/>one JSON model per build")]
dataDir --> model["src/generate/model.js<br/>indexes, hierarchy, callers"]
model --> routes["src/generate/routes.js<br/>the site map"]
routes --> render["src/generate/render/<br/>one file per kind of page"]
render --> distDir[("dist/<br/>the static site")]
routes --> dev["src/dev.js<br/>one page per request"]
siteAssets["site/<br/>CSS + client JS"] --> distDir
siteAssets --> dev
src/generate/routes.js is the single site map. The generator walks it to
write every page; the dev server looks one page up in it by URL. Neither can
drift from the other, because there is only the one list.
There are no .html files. Every page is a JavaScript function returning a
template literal, one file per kind of page. The fastest way to find the one
you want is to open the page under npm run dev and view source: line two
names the file.
| URL | File |
|---|---|
/ |
src/generate/render/home.js |
/topics/, /topics/<Name>/ |
src/generate/render/topics.js |
/classes/ |
src/generate/render/hierarchy.js |
/classes/index/, /classes/<letter>/, /classes/members/… |
src/generate/render/classes.js |
/classes/<Name>/, /classes/<Name>/members/ |
src/generate/render/class.js |
/enum/<Name>/, /globals/… |
src/generate/render/globals.js |
/files/, /files/<Dir>/<Name.c>/ |
src/generate/render/files.js |
/changelog/ |
src/generate/render/changelog.js |
/community/ |
src/generate/render/community.js |
/about/ |
src/generate/render/about.js |
| 404 | src/generate/render/notfound.js |
Everything around a page body — <head>, the header, the nav bar, the search
palette — is layout() in src/generate/html.js, which is also
where signatures and doc comments are rendered. The pieces more than one page
needs (source links, "Referenced by") are in src/generate/render/shared.js.
src/generate/render.js is a barrel that re-exports the lot, so importers
need not know which file a renderer is in.
Everything that acts on the page you are already on — the filter field, the
A–Z, expand and collapse, the tabs to a page's siblings — is one bar under
the site nav, built by pageBar() in src/generate/render/pagebar.js. A page
asks for the parts it wants and passes the result to layout() as bar,
which hangs it below the header, outside <main>, so it spans the window.
A new control goes in that one file rather than in each renderer that wants
it. Being outside the body, it travels with an archived page in the meta line
rather than in the stored body; see ARCHIVE_MARK in src/generate/html.js.
HTML-bearing template literals are prefixed with /* html */. It costs
nothing at runtime, and editors with the common
es6-string-html
extension will syntax-highlight and format the markup inside them.
Hand-written prose that is not derived from a build — the homepage blurbs, the
community links, the about page, the release threads — is in src/generate/content.js.
site/app.js is a list of feature inits, one line each. Every feature is a
module beside it in site/app/, and each is guarded by whether the thing it
works on is on the page, which is how one script serves ~660,000 pages.
| Feature | File |
|---|---|
| Light/dark, the wordmark | site/app/theme.js |
| The nav bar | site/app/nav.js |
| Which build this is, the version switcher | site/app/builds.js |
| The search palette | site/app/search.js |
search.json, shared by four features |
site/app/search-index.js |
| Recent & pinned pages | site/app/recent.js |
The ? shortcuts list |
site/app/shortcuts.js |
| Enforce Script highlighting | site/app/highlight.js |
| A source page: links, folding | site/app/source.js |
| "Added in" badges, the History timeline | site/app/history.js |
| Community notes | site/app/notes.js |
| What a signature keyword means, on hover | site/app/glossary.js |
| Copy buttons, the override stub | site/app/copy.js |
| "Copy for LLM" on a class or enum | site/app/llm.js |
| The page bar under the nav | site/app/pagebar.js |
| Arrow keys on the files tree | site/app/tree.js |
| The member and field tables | site/app/members.js |
| Table of contents | site/app/toc.js |
| The source minimap | site/app/minimap.js |
| The changelog, loaded on demand | site/compare.js |
Four modules are not features: site/app/dom.js is the handful of helpers
and the page facts everything reads off <body>, site/app/search-index.js
loads search.json once for whoever asks, site/app/overlay.js keeps two
overlays from being open at the same time, and site/app/pill.js is the
travelling highlight the rail and the version switcher both light their rows
with.
Styles enter at site/styles.css, which @imports the sheets under
site/styles/ in cascade order — one file per UI surface, same grain as
site/app/. Edit the file that owns the thing you are changing; do not
reorder the imports. Tailwind CLI compiles that entry to .cache/styles.css
(npm run css:build / css:watch). Prefer unprefixed Tailwind utilities
(flex, text-sm, bg-accent) for layout, spacing, and type. Keep named
classes (chip, side, badge, …) for shared controls the catalogue and
client JS hook into — those stay in site/styles/ when they are not
utility-shaped.
| Area | File |
|---|---|
| Colours, measures, type | site/styles/tokens.css |
html / body / links |
site/styles/base.css |
| Side rail | site/styles/rail.css |
| Icons | site/styles/icons.css |
| Search palette and results | site/styles/search.css |
| Keyboard shortcuts overlay | site/styles/shortcuts.css |
| Build / version picker | site/styles/builds.css |
| Page shell and nav | site/styles/shell.css |
| Travelling highlight | site/styles/pill.css |
| Member docs and cross-refs | site/styles/members.css |
| Source minimap | site/styles/minimap.css |
| Page content | site/styles/content.css |
| Credits page | site/styles/credits.css |
| Badges | site/styles/badges.css |
| Tables and catalogs | site/styles/tables.css |
| Class page | site/styles/class.css |
| Per-type history | site/styles/history.css |
| Tooltips | site/styles/tooltips.css |
| Note form | site/styles/notes.css |
| Source view | site/styles/source.css |
| Code folding | site/styles/fold.css |
| Chips and title actions | site/styles/chips.css |
| Share line range | site/styles/share.css |
| Page filter | site/styles/filter.css |
| Page bar | site/styles/pagebar.css |
| Files column | site/styles/filetree.css |
| Trees | site/styles/trees.css |
| Compare two builds | site/styles/compare.css |
| Inset layout | site/styles/inset.css |
| Responsive | site/styles/responsive.css |
site/notfound.js and site/archive.js are separate entry points, loaded only
by the 404 page and the archive shell.
site/app.js is loaded as <script type="module">, so site/app/ has to
reach the browser as a directory: the generator copies site/ recursively into
dist/assets/, and the dev server serves subpaths under /assets/.
src/parser/ reads Enforce Script — a preprocessor pass, a lexer, and a
recursive-descent pass that produces the per-file model. It is documented in
the files that implement it, and test/parser.test.js is the specification in
practice. After changing it, re-run with FORCE_PARSE=1 npm run parse (or
ONLY_VERSION=1.29 for one build) so the commit-sha cache is not reused.
No runtime dependencies. Node's standard library only, in both the
generator and the browser. There is no bundler and no build step for the
client: site/ is copied to dist/assets/ as it is.
Node 20+, ES modules. In src/, in site/, and in test/.
A page's bytes must depend only on its content. Not on which build
rendered it, not on the date, not on the state of the rest of the site map.
That is what lets dist/ hard-link identical pages across ~49 builds instead
of storing each one again, and it is why the build number, the topic list and
the "added in" badges are all fetched by the client rather than written into
the HTML. test/render.test.js guards it; if you find yourself wanting to put
a version number on a page, read the comment on layout() first.
Everything a page can compute, it computes. The all-members table, the
data-fields index and the source page's cross-links are all built in the
browser from search.json, which the page fetches for the search palette
anyway. Inlining the all-members table alone costs 564 MB per build.
npm test runs node --test over test/. There is no framework and no
config. Worth knowing about:
test/render.test.js— the byte-identity invariant abovetest/routes.test.js— every page is reachable by URL, and no two claim onetest/app.test.js— runs the whole client against a DOM where nothing is found, which is the only way most of it is exercised at alltest/parser.test.js— the Enforce Script grammar, by example
Small and focused is easiest to review. Say what changed and why; if it is a
rendering change, a before/after screenshot helps. Run npm test first.
Bugs and questions: Discord, or open an issue.