Decision — site URL layout
Status: accepted 2026-10-16, after review by two independent reviewers.
The question
mixos.dev serves two readers from the same files. A person in a browser
gets a static HTML page, and nothing needs JavaScript to render. An agent
or script on any machine fetches the same page as Markdown and starts from
/llms.txt. MixOS will grow to hundreds of apps, services, libraries and
tools over the next few years. How should the URL space be organised, so
that an address, once published, never has to change?
The decision
Two namespaces
- Each component's documentation is at the top level, under the
component's name. Examples:
/mix/,/term/,/compd/,/hostd/,/noded/. - Everything that is not a component lives under
/docs/. That covers guides such as/docs/try/and/docs/drive/, plus/docs/decisions/,/docs/spec/,/docs/dev/,/docs/releases/, and the generated indexes below. The site's global navigation has a Docs entry that leads there.
Component names
- A component's name is the
componentdeclared by its principal package in[package.metadata.mixos], or by itscomponent.mx. Component names are already plain, unique across the workspace and never reused (see source layout), so an address can be built from the name alone. - A component name starts with a lowercase letter and contains only lowercase letters, digits and hyphens.
- Two names are reserved:
docsandv. The component metadata gate refuses them, so a forbidden name is caught when the component is created, not when the site is built. - Site machinery uses names no component can take. Generator inputs and
assets live in
_build/and_web/(renamed frombuild/andweb/), and root files (index.html,404.html,llms.txt,llms-full.txt,sitemap.xml,robots.txt,CNAME, the favicons) all contain a dot or start with an underscore. - Mix's names. The
mixcomponent is the language library, so/mix/is the Mix language manual and the same pagesmix man TOPICreads. Themixbinary's component issystem(/system/), and the shell ismix-shell(/mix-shell/). Each links to the others.
Where pages come from, and their addresses
The path of a source file relative to the site root (the repository's
docs/ directory) determines its address. The table gives every
mapping:
| Source | HTML | Markdown |
|---|---|---|
docs/<name>/README.md | /<name>/ | /<name>/README.md |
docs/<name>/<page>.md | /<name>/<page>/ | /<name>/<page>.md |
docs/docs/<page>.md | /docs/<page>/ | /docs/<page>.md |
<component dir>/CHANGELOG.md (mirrored) | /<name>/changelog/ | /<name>/changelog.md |
| Generated index pages | /docs/<index>/ | /docs/<index>.md |
- The Markdown is published at its source path. Relative links inside
it therefore resolve correctly under ordinary URL resolution. An agent
builds a component's Markdown address as
/<name>/README.md. The generator checks every relative link in both formats. - Hand-written component documentation lives in
docs/<name>/. That is its one canonical copy. The component's own directory keeps a shortREADME.mdthat points to the page, so someone browsing the source finds it. CHANGELOG.mdstays beside the code, where releases update it. Only the principal package's changelog is published. The published copy is generated and never edited. Relative links in it are rewritten to the source on GitHub. A component with no changelog has no changelog page.changelogis therefore a reserved page name inside every component.- The doubled
docs/docs/is accepted. It keeps the rule that the source path determines the address, without a rename table. - Generated pages have a Markdown twin like every other page.
- HTML is the canonical address. Each HTML page names its Markdown as
<link rel="alternate" type="text/markdown">.sitemap.xmllists HTML only.
Navigation from metadata
- The URL never encodes kind, layer or install profile. These change over a component's life: a library is promoted, or a desktop service becomes useful headless.
/docs/desktop/and/docs/server/are generated index pages listing components by their declared installprofiles. A component in both is listed in both, with one page. Components in thebaseprofile are listed under a reader-facing heading such as "Core components". The wordbasestays in the installation docs./docs/components/is the one complete, generated component index, giving each component's name, kind, profiles, source path and page. It replaces the hand-rundocs/docs/dev/components.md(formerlydocs/dev/components.md).- Until
profilesis declared everywhere, the indexes list undeclared components as unclassified. They never guess fromlayer.
Agents
/llms.txtlists every section, with absolute Markdown URLs: one line per component (with its kind and profiles) and one per page under/docs/. Every published document can be reached from it.llms-full.txtremains an optional single file.- Every page states that it describes the current
mainbranch.
Every component has a page
- The principal package of every component (app, service, cli or lib) and
every Mix component must have
docs/<name>/README.md. Private crates andtoolandtestpackages are excluded. - A component without a page fails the site gate. While existing
components are being documented, the ones still missing are named in an
exception list in
site.conf.mix. That list may only shrink, and a new component cannot be added to it.
Addresses are permanent
From the day this decision is accepted:
- A profile change or a source move never changes an address.
- An address that goes away keeps working in both formats: an entry in
site.conf.mix'sredirectsfor browsers, plus a short Markdown page at the old.mdaddress that names the new one, because an agent fetching Markdown does not follow an HTML redirect. - A split leaves a page that links to its successors. A retired component
keeps a retirement page, which stays listed in
/llms.txt, and its name is never reused.
Versions
/v/ is reserved for versioned copies of the site. How they work is
decided before the first stable release.
Why
- One namespace for components, one for everything else. An address
either names a component or starts with
/docs/. Only two names are off limits for components. - Names are the most stable thing a component has. The source-layout decision rejected splitting the source tree by desktop, server or core, because a component could plausibly sit in either of two places and moves between them. Putting that axis into URLs would have recreated the problem where a move is most costly.
- It matches what already works.
/mix/and the existing component pages (/hostd/,/noded/,/prefs/,/backdrop/,/screenshot/,/dopus/) are already at the top level, andmix manalready fetches/mix/<topic>.mdand/mix/README.md. - One canonical copy. Keeping hand-written docs in
docs/rather than beside the code relaxes the source-layout principle that a component is self-contained. That is deliberate: documentation for readers has one home, one navigation and one gate. Code, tests and changelogs stay with the component.
Rejected
- Mirror repository paths (
/apps/term/,/libs/bus/). The address is predictable, but it encodes kind, and it moves when a component moves in the source tree. - Sections by install profile (
/desktop/term/,/server/maild/,/base/noded/). Profiles are deployment data that change, a component can be in two, andserveris empty today. A profile change would move a public URL. - A component prefix (
/c/term/or/components/term/). It protects the component namespace, but/docs/does the same job from the other side, with shorter, more readable component URLs. - Docs beside the code, mirrored into the site. That makes two copies, needs a freshness gate and a mapping table, and splits navigation.
- A
/<name>.mdalias for each component page. Relative links in it would resolve against the wrong directory. - Mapping
/docs/to a differently named source directory (for exampledocs/guides/). It avoidsdocs/docs/, but it adds a permanent rename table.
Consequences
- Nobody depends on the current URLs, so this one move keeps no old address. The permanence rules apply from acceptance.
- The root pages that are not components move under
docs/docs/:tryanddrive, plusdecisions/,spec/anddev/. - Root pages that describe a component become its page
(
docs/hostd.mdbecomesdocs/hostd/README.md, and so on). Four were named after a topic rather than their component, and take the component's name:editbecomeseditd,portalbecomesportald,bridgebecomesbridgedandreleasesbecomesreleasesd. (The first draft of this record listededit,bridgeandreleasesas non-component pages; each documents one service.) - The component READMEs beside the code move into
docs/<name>/README.md, leaving a pointer behind. The CHANGELOGs stay where they are. docs/build/anddocs/web/becomedocs/_build/anddocs/_web/.- The component metadata gate gains the name grammar and the reserved names.
gen-sitegains the output-collision check (every HTML, Markdown, asset and generated output), the changelog mirror, the generated indexes and their Markdown twins, the alternate links, the missing-page gate with its exception list, and a groupedllms.txt.- Every link to a moved page is updated, including in AGENTS.md and the decision records.