docs(options): emit markdown only, render HTML in website

Per #1407 review: the options reference should be markdown here; the
HTML + CSS belongs in the website repo where the theme lives and the
stylesheet can be shared with /docs.

- nix/docs/default.nix: drop the cmark-gfm HTML rendering, the inline
  <style>, and the HTML index. Emit index.md + host.md + agent.md only
  (CommonMark from nixosOptionsDoc).
- Remove nix/docs/style.css.
- Update docs/gotchas.md + CLAUDE.md to describe the md-only output and
  point at the website for HTML rendering.

The website (hyperhive/website#26) renders these .md to themed HTML for
/options/, sharing one docs.css with the prose /docs/ tree.
This commit is contained in:
iris 2026-06-05 19:29:07 +02:00 committed by mara
commit 60042d877e
4 changed files with 48 additions and 186 deletions

View file

@ -348,10 +348,10 @@ nix/
— weston + VNC backend systemd unit; writes
/etc/hyperhive/gui.json (vnc_port + auth) for
the harness WebSocket relay (/screen/ws)
docs/ HTML+MD options reference (host + agent); built
default.nix via `nix build .#docs` → static site
style.css (`index.html`, `host.html`, `agent.html`,
`host.md`, `agent.md`); rendered by cmark-gfm
docs/ markdown options reference (host + agent);
default.nix `nix build .#docs``index.md` + `host.md`
+ `agent.md` (CommonMark from nixosOptionsDoc).
HTML+CSS rendered downstream by the website repo
forge-theme/theme-catppuccin-vibec0re.css Catppuccin Mocha forge theme
docs/

View file

@ -286,29 +286,24 @@ inputs stay out of the eval) and `agentEval` (reuses the already-evaluated
`agent-base` container config so the per-agent options tree is
identical to what a real agent container sees).
Three output trees consumed by `flake.nix`:
Three output trees consumed by `flake.nix`, all **markdown**:
- `docs-host` — operator-facing host module options
(`services.hyperhive.*`)
- `docs-agent` — per-agent harness options (`hyperhive.*`
declared in `nix/templates/harness-base.nix`)
- `docs` — bundled static site (`index.html` + `host.html` +
`agent.html`, plus `.md` source-of-truth versions of each
options page)
- `docs` — bundle of `index.md` + `host.md` + `agent.md`
Rendering pipeline:
Pipeline:
- CommonMark from `nixosOptionsDoc.optionsCommonMark` — source of
truth, kept as `.md` in the bundle.
- HTML via `pkgs.cmark-gfm` over the CommonMark, wrapped in a
minimal inline-CSS template. `cmark-gfm` (not plain `cmark`) so
any future tables / autolinks Just Work without revisiting.
- Inline `<style>` from `nix/docs/style.css` so the bundle is
single-file-per-page and nginx's `/options/` mount needs no MIME
setup for separate `.css` files and no cache-busting.
- Asset paths inside rendered HTML are all relative
(`./host.html`, etc.) so the bundle can mount at any URL prefix
without rewriting.
- CommonMark from `nixosOptionsDoc.optionsCommonMark` is the only
output — the source of truth, emitted as `.md`.
- **HTML + CSS is rendered downstream by the website repo**
(`nix/options.nix` there), which consumes this bundle's `host.md` /
`agent.md`, renders them with `cmark-gfm`, and shares one
stylesheet (`docs.css`) across `/options/` and the prose `/docs/`
tree. Keeping rendering in the website means the theme has a single
home and the colours are shared.
- `transformOptions` strips the nix-store prefix from option
declaration paths and rewrites them as forge URLs, so the
rendered docs link back to the source.

View file

@ -5,10 +5,11 @@
nixosSystem,
}:
# Nix options reference: `pkgs.nixosOptionsDoc` over two evaluated
# module trees, rendered as CommonMark + HTML + bundled static site
# the operator's nginx serves from `/options/`. Full pipeline +
# subtree-pick / output-tree rationale: docs/gotchas.md::Nix options
# reference.
# module trees, emitted as CommonMark (`host.md` + `agent.md`). The
# HTML + CSS for `/options/` is rendered downstream by the website
# repo, which owns the presentation and shares one stylesheet with the
# prose `/docs/` tree. Full pipeline + subtree-pick / output-tree
# rationale: docs/gotchas.md::Nix options reference.
let
# Stub host system: every hyperhive subsystem `mkForce false` so
# heavy build inputs (matrix container, forge, etc.) stay out of
@ -102,7 +103,10 @@ let
inherit transformOptions;
};
# CommonMark .md = source of truth; HTML is rendered from this.
# CommonMark `.md` is the source of truth and the only output. HTML
# rendering + theming lives in the website repo (it owns the
# presentation + shares one stylesheet across `/options/` and
# `/docs/`); this flake just emits the option reference as markdown.
mkMarkdownPage =
name: title: doc:
pkgs.runCommand "hyperhive-${name}.md" { } ''
@ -117,100 +121,39 @@ let
} > $out
'';
# Loaded as text so it's editable with normal CSS tooling and
# inlined into every page (no second-fetch dependency).
styleCSS = builtins.readFile ./style.css;
# Bundle landing page — a short markdown index pointing at the two
# reference pages.
indexMD = pkgs.writeText "hyperhive-options-index.md" ''
# hyperhive — nix options reference
# CommonMark → cmark-gfm → minimal template, inline CSS, relative links.
mkHtmlPage =
name: title: doc:
pkgs.runCommand "hyperhive-${name}.html" { nativeBuildInputs = [ pkgs.cmark-gfm ]; } ''
{
echo '<!doctype html>'
echo '<html lang="en">'
echo '<head>'
echo '<meta charset="utf-8">'
echo '<meta name="viewport" content="width=device-width, initial-scale=1">'
echo '<title>${title}</title>'
echo '<style>'
cat ${pkgs.writeText "hyperhive-docs-style.css" styleCSS}
echo '</style>'
echo '</head>'
echo '<body>'
echo '<nav>'
echo '<a href="./index.html">index</a>'
echo '<a href="./host.html">host options</a>'
echo '<a href="./agent.html">agent options</a>'
echo '</nav>'
echo '<main>'
echo '<h1>${title}</h1>'
echo '<p><em>Auto-generated from the hyperhive flake. Source: <a href="${forgeRoot}/nix/docs/default.nix">nix/docs/default.nix</a>.</em></p>'
cmark-gfm ${doc.optionsCommonMark}
echo '</main>'
echo '<footer>'
echo '<p>hyperhive · <a href="${forgeRoot}">source</a></p>'
echo '</footer>'
echo '</body>'
echo '</html>'
} > $out
'';
Auto-generated from the hyperhive flake. Two reading paths:
# Landing page — same template shape, hand-authored intro + cross-links.
indexHTML = pkgs.runCommand "hyperhive-docs-index.html" { } ''
{
echo '<!doctype html>'
echo '<html lang="en">'
echo '<head>'
echo '<meta charset="utf-8">'
echo '<meta name="viewport" content="width=device-width, initial-scale=1">'
echo '<title>hyperhive nix options reference</title>'
echo '<style>'
cat ${pkgs.writeText "hyperhive-docs-style.css" styleCSS}
echo '</style>'
echo '</head>'
echo '<body>'
echo '<nav>'
echo '<a href="./index.html">index</a>'
echo '<a href="./host.html">host options</a>'
echo '<a href="./agent.html">agent options</a>'
echo '</nav>'
echo '<main>'
echo '<h1>hyperhive nix options reference</h1>'
echo '<p>Auto-generated from the <a href="${forgeRoot}">hyperhive flake</a>. Two reading paths:</p>'
echo '<h3><a href="./host.html">host options</a></h3>'
echo '<p>Options exposed by <code>hyperhive.nixosModules.default</code> to operator host configurations: <code>services.hyperhive.{enable,domain,c0re,forge,matrix,gateway}.*</code>.</p>'
echo '<h3><a href="./agent.html">agent options</a></h3>'
echo '<p>Per-agent options declared in <code>nix/templates/harness-base.nix</code> and visible from every <code>agent.nix</code>: <code>hyperhive.model</code>, <code>hyperhive.allowedRecipients</code>, <code>hyperhive.extraMcpServers</code>, <code>hyperhive.frontend.*</code>, <code>hyperhive.forge.*</code>, <code>hyperhive.matrix.*</code>, <code>hyperhive.gui.*</code>.</p>'
echo '<h3>Regenerate</h3>'
echo '<pre><code>nix build .#docs # bundled static site (index + host + agent)'
echo 'nix build .#docs-host # host page only (HTML)'
echo 'nix build .#docs-agent # agent page only (HTML)</code></pre>'
echo '<p>The bundle also ships <code>.md</code> versions of each page (<code>host.md</code>, <code>agent.md</code>) same content, source-of-truth shape alongside the HTML.</p>'
echo '</main>'
echo '<footer>'
echo '<p>hyperhive · <a href="${forgeRoot}">source</a></p>'
echo '</footer>'
echo '</body>'
echo '</html>'
} > $out
- [host options](host.md) options exposed by
`hyperhive.nixosModules.default` to operator host configurations
(`services.hyperhive.{enable,domain,c0re,forge,matrix,gateway}.*`).
- [per-agent options](agent.md) options declared in
`nix/templates/harness-base.nix`, visible from every `agent.nix`
(`hyperhive.model`, `hyperhive.allowedRecipients`,
`hyperhive.extraMcpServers`, `hyperhive.frontend.*`,
`hyperhive.forge.*`, `hyperhive.matrix.*`, `hyperhive.gui.*`).
Regenerate with `nix build .#docs` (bundle), `.#docs-host`, or
`.#docs-agent`.
'';
hostHTML = mkHtmlPage "docs-host" "hyperhive host options" hostDoc;
agentHTML = mkHtmlPage "docs-agent" "hyperhive per-agent options" agentDoc;
hostMD = mkMarkdownPage "docs-host" "hyperhive host options" hostDoc;
agentMD = mkMarkdownPage "docs-agent" "hyperhive per-agent options" agentDoc;
in
{
host = hostHTML;
agent = agentHTML;
host = hostMD;
agent = agentMD;
# Bundled static site nginx serves at `/options/`. Asset paths are
# all relative so the prefix can change without rebuild.
# Bundle of the option reference as markdown. The website renders
# these `.md` to themed HTML for `/options/`; standalone consumers
# get the CommonMark source directly.
bundle = pkgs.runCommand "hyperhive-options-docs" { } ''
mkdir -p $out
cp ${indexHTML} $out/index.html
cp ${hostHTML} $out/host.html
cp ${agentHTML} $out/agent.html
cp ${indexMD} $out/index.md
cp ${hostMD} $out/host.md
cp ${agentMD} $out/agent.md
'';

View file

@ -1,76 +0,0 @@
:root {
color-scheme: light dark;
--fg: #1a1a1a;
--bg: #fafafa;
--muted: #6b6b6b;
--accent: #5e548e;
--code-bg: rgba(94, 84, 142, 0.08);
--rule: rgba(94, 84, 142, 0.2);
}
@media (prefers-color-scheme: dark) {
:root {
--fg: #e6e6e6;
--bg: #0d0d0d;
--muted: #9b9b9b;
--accent: #c4a7e7;
--code-bg: rgba(196, 167, 231, 0.08);
--rule: rgba(196, 167, 231, 0.2);
}
}
* { box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
max-width: 56rem;
margin: 0 auto;
padding: 2rem 1.25rem 4rem;
color: var(--fg);
background: var(--bg);
line-height: 1.55;
}
nav {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--rule);
font-size: 0.9rem;
color: var(--muted);
}
nav a { color: var(--accent); text-decoration: none; }
nav a:hover { text-decoration: underline; }
nav a + a { margin-left: 0.5rem; }
nav a + a::before { content: "· "; color: var(--muted); margin-right: 0.25rem; }
h1, h2, h3 { color: var(--accent); line-height: 1.25; }
h1 { font-size: 1.75rem; margin-top: 0; }
h2 {
font-size: 1.15rem;
margin-top: 2.5rem;
padding-top: 0.5rem;
border-top: 1px solid var(--rule);
font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
}
h3 { font-size: 1rem; }
p { margin: 0.75rem 0; }
code {
background: var(--code-bg);
padding: 0.1em 0.35em;
border-radius: 0.25em;
font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
font-size: 0.9em;
}
pre {
background: var(--code-bg);
padding: 0.85rem 1rem;
border-left: 2px solid var(--accent);
border-radius: 0 0.25rem 0.25rem 0;
overflow-x: auto;
line-height: 1.4;
}
pre code { background: transparent; padding: 0; }
a { color: var(--accent); }
em { color: var(--muted); font-style: normal; font-size: 0.9em; }
footer {
margin-top: 4rem;
padding-top: 1rem;
border-top: 1px solid var(--rule);
font-size: 0.85rem;
color: var(--muted);
}