Changelog
Notable changes to LuauDocs, newest first. The format follows Keep a Changelog and versions follow semantic versioning: while the major version is 0, a minor bump may carry breaking changes.
0.2.0 - 2026-09-01
Added
_category_sidebar sidecars for guide folders. A_category_.json(or_category_.yml) beside a folder's pages names, places, and collapses the group it becomes, reading thelabel,position,collapsed, andcollapsiblethat Docusaurus and Moonwave already wrote there. Settingcollapsibletofalsedrops the toggle for a group that is always open. A folder with anindex.mdcan putsidebar_position,collapsed, andcollapsiblein that page's frontmatter instead, and the sidecar outranks it key by key. A position is any number Docusaurus accepts, so a migrated2.5still slots between 2 and 3. A sidecar that does not parse fails the build, named.init --from-moonwavenow copies these files across instead of reporting them as unportable, naming only the keys the sidebar does not read (link,className); one that does not parse aborts the conversion before anything is written, as a malformedmoonwave.tomldoes.- Underscore-prefixed guide folders and pages are left out, as Docusaurus leaves them out: not in the sidebar, not built, not in search or
llms.txt. A_drafts/folder or a_partial.mdfor<!--@include: -->no longer lists itself.
Changed
devreloadsluaudocs.tomlin place instead of exiting and asking to be restarted. The config is re-read, the watchers rebind to whatever[source] entriesnow names, and the rebuilt site config is what the VitePress server restarts itself on. A config that does not parse keeps the session running on the last one that did, so a half-typed edit costs an error line rather than the server, and so does aluaudocs.tomlthat a stash or a branch switch takes away. Moving[docs] dirstill ends the session, since the server is bound to the directory it started on.sidebar_positionis read against a page's siblings alone, so the pages in a subdirectory number from 1 rather than continuing the count outside it. A group used to take the lowest position among its children, which meant placing a folder required numbering its pages in the parent's sequence; a group is now placed by its own_category_file orindex.md, and one that declares no position sorts last. A site that relied on the old behavior should give each folder a_category_.jsoncarrying the position its pages used to imply.- An
index.mdleads the folder it sits in, ahead of any position a sibling declares, and at the top ofguide/too: aguide/index.mdnow opens the Guide sidebar whateversidebar_positionit carries, where it used to sort by that position like any other page. An index page's position places its folder among the folder's siblings, so on the top-level one it places nothing. - A guide folder heads its own sidebar section when nothing sits loose. When
guide/holds only folders, the single "Guide" heading over them is dropped and each folder becomes a top-level section, so a site organized entirely into folders no longer reads as one group of groups. A section head has no toggle unless its_category_file orindex.mdsetscollapsedorcollapsible. Aguide/with any page directly inside it keeps the "Guide" heading it had. - The nested sidebar groups above the current page take the brand color, the same as the entry for the page itself. VitePress only lifted them from the muted text color to the plain one, which left the open group looking like every other. Section heads keep the plain color, and light only when they link to the current page.
Fixed
- A frontmatter key left blank (
title:on a line of its own) no longer reads the line below it as its value.
0.1.0 - 2026-08-28
First public release.
Added
- Surface discovery without tags. The extractor symbolically evaluates a module's top-level statements down to its return value, so functions, methods, properties, nested classes, re-exports, and
export types are documented from the code itself. Table literals,__indexclass idioms,setmetatableandtable.freezewrappers, cast types, member imports, and conditional mounts are all recognized. A leading underscore keeps a member out of the site, with@private,@ignore, and[api] includePrivateas the overrides. - Badges detected from code. A function that waits (
task.wait,signal:Wait(), a yielding engine method, apcallrunning one, or a call to something that does, followed across modules, imports, passthroughs, and__callmetatables) is badged Yields without a tag. Remote calls, realm-only services, Studio-only members, and thepluginglobal badge a function Server, Client, or Plugin, and a prop renders Read Only when its table istable.freezed or its field is markedreadin the table's type. The tags stay as overrides: a realm tag replaces whatever was detected. - Parameter docs from trailing comments. In a multi-line signature, the trailing
-- commenton a parameter's line documents that parameter, the way it already documents a property or a type field;@paramoutranks it. - Signatures from real type annotations, spelled the way you wrote them. Every type mention links to its definition, and Roblox types link to the creator docs.
- Property types read off the value. A property nothing annotates still documents its type when the value settles it: literals and comparisons,
Color3.new(...)and other datatype constructors,Enum.X.Y, an instance path, an engine method called on a service, a list of literals, and a call into your own code, which takes the callee's annotated return and links to it (across modules, so a library of.newconstructors needs no tags). A value that settles nothing (a + b,script.Name) stays untyped rather than guessing, and an annotation, a cast, or a floating@propalways wins over what was inferred. - Aliases link to what they alias. One table reached by two names in a module gets one documented section, and the other name is typed as a link to it rather than rendering bare beside it. The cross-module case is still a re-export, which names the hop it crossed.
- Conditional mounts badge their realm. A property written
RunService:IsServer() and script:WaitForChild("Storage")is badged Server and typedInstance?from that one expression: the guard says where the value exists, the guarded half says what it is. A value guarded by an already-guarded one carries the same realm. A guard over a plain boolean is a predicate rather than a mount, so it keeps itsbooleantype and no badge, since the other realm can read it and find it false. - References that link themselves. A
[State:Connect]in a doc comment becomes a link, resolved against your project,@externaldeclarations, and Roblox names; one that resolves to nothing stays plain text. When one spelling could mean two things, a module outranks a same-named type and a re-spelling alias cedes to the declaration it re-spells; only a genuine tie is left unlinked, reported with both targets. - No toolchain to install. Node is the only requirement. The extractor runs on a pinned Lute that the first extraction downloads, verifies against a checksum, and caches per user.
LUAUDOCS_LUTEpoints LuauDocs at a Lute build of your own, for an offline machine or a platform with no prebuilt binary. - Moonwave compatibility. All 24 Moonwave tags keep working as overrides, so tagged sources need no edits.
luaudocs init --from-moonwaveconverts the config (moonwave.tomlormoonwave.json, navbar and footer included), copies.moonwave/static/, and ports the hand-written pages:docs/lands underguide/and the markdown underpages/at the site root, with.mdxrenamed, MDX imports and comments stripped, and links, static-asset paths, and fence titles respelled, while draft and unlisted pages stay unpublished. The homepage banner and feature cards become anindex.mdin VitePress's home layout, and.moonwave/custom.cssarrives with the known Infima variables renamed. Whatever has no equivalent is named in a report rather than guessed at. Docusaurus markup converts at render time, in doc comments and guide pages alike::::noteand:::cautionbecome VitePress containers (bracket titles unwrapped), and<Tabs>/<TabItem>groups become VitePress's own tab strip. - A generated VitePress 2 site, wired up from
luaudocs.toml: local search, dark mode, Luau highlighting in both fences and inline`code`{luau}spans, tool and file-type icons on code-group tabs and fence titles,[[site.head]]for analytics and verification tags,sitemap.xmlfrom[docs] url, andllms.txtplusllms-full.txtat the site root.<docs>/api/and<docs>/.vitepress/are wholly tool-owned and rebuilt by every build; your content sits beside them and is scaffolded only once. - Fira Code for code, in fences and inline spans both, with its programming ligatures on. The font ships with the tool and is served from your own site (no request to a font CDN, and no network in
dev), as one variable file covering every weight. Set--vp-font-family-monoincustom.cssto use something else. - Your README and CHANGELOG become pages. While the docs directory has no
index.md, every build makes the landing page from yourREADME.md, marked as generated until you claim the file, andCHANGELOG.mdrenders at/changelog. - Source links from
[repo] url, whichinitfills in from your git remote: every entry links to the lines that produced it, and the site gets edit links and the header's GitHub icon. - Navigation that mirrors usage. The sidebar is the access-path tree derived from the code: pages group by how a caller reaches a module rather than by folder layout, falling back to the Rojo instance tree (or the folder layout) for modules that nothing exposes. There are no keys to reorder, pin, or rename groups. Two modules sharing a name stay distinguishable: the later gets a numbered URL and carries its parent in the sidebar, search, and tab title (
Defaults (Flux)). Every page also heads with a trail of links to what is above it (Home › Overview › Flux › Utilon an API page,Homeand the enclosing folders on a guide), so a reader arriving from search or a deep link sees where the page sits. It renders above the title, which is where VitePress's search indexer stops reading, so navigation never enters the index. - Type entries. Every exported type, and any local type a documented signature mentions, becomes a documented entry with per-field descriptions, taken from doc comments above fields or trailing comments on them. A
type functiondocuments as its callable head with parameters, the body behind the source link. A tag-written type name resolves against the modules the file actually binds, never an unrelated module's same-named class. - Require resolution across string requires,
.luaurcaliases, Roblox instance paths, Rojo*.project.jsonmounts, and nested projects. - CLI:
init,dev(watch plus HMR), andbuild(--emit-only,--strict,--url,--model);luaudocs build --emit-only --strictis the CI validation.initalso scaffolds the GitHub Pages workflow at.github/workflows/docs.ymlwhen the file does not exist. - Extraction never fails silently. Anything LuauDocs could not do becomes a diagnostic with a location, severity, and code, and
--strictpromotes warnings to failures. An error build refreshes the pages that did emit and withholds every destructive step, so a transient typo cannot look like an intentional removal. <Frame>, a labeled border for quoting generated output in a guide. Paired with a VitePress file inclusion, it embeds an entry from your ownapi/pages by stable region name, and a name that stops existing fails the build instead of quietly emptying the frame. See Reference: Markup.- The doc model as an output.
luaudocs build --model <file>writes the JSON the renderer consumed: every module, member, type, and signature, with type references resolved to the ids of the declarations they name, and diagnostics alongside.schemaVersionis1, and the shape changes in place while the major version is0. See the doc model reference. - Guide-only mode (
[source] entries = []) for projects with no Luau surface: the extractor never runs and no Lute is fetched.