From addeae309824c0855b170865954849ee59507310 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 14:05:33 +0200 Subject: [PATCH 1/2] docs: publish plugin-env.md on the options-doc Pages site (#614) Step 4 of #573, deferred by PR #574 until #570's flake.nix/workflow changes landed (they did, 2026-07-28). docs/plugin-env.md was readable only in the repo while the options reference next to it was published. Teaches the options-doc derivation (flake.nix) to render plugin-env.md too, rather than doing it in the workflow: it reuses the exact same cmark rendering path as options.html, factored into a small render_page shell function, so both pages share one source of styling truth. The cmark tool swaps to cmark-gfm (still tiny, no pandoc/Haskell closure) because plugin-env.md's env-var tables are GFM pipe tables, which plain CommonMark has no syntax for and would otherwise print literally. Since there are now two documents, index.html stops being a byte-copy of options.html and becomes a small generated landing page linking both. options-doc.yml stays a dumb copier: it now copies plugin-env's .md/.html and the derivation's own index.html instead of re-deriving one from options.html. Closes #614 Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/options-doc.yml | 20 ++++--- flake.nix | 92 ++++++++++++++++++++++++------- 2 files changed, 86 insertions(+), 26 deletions(-) diff --git a/.github/workflows/options-doc.yml b/.github/workflows/options-doc.yml index 9acd0f7..3e352b2 100644 --- a/.github/workflows/options-doc.yml +++ b/.github/workflows/options-doc.yml @@ -1,9 +1,12 @@ name: options doc → GitHub Pages -# Publishes the autogenerated `programs.trollshell.*` options reference -# (`nix build .#options-doc`, added in #533) to GitHub Pages. Phase 2 of -# #533 — see #540. Deliberately a leaf: it is NOT part of `nix flake check` -# and doesn't gate any other workflow. +# Publishes the trollshell docs site (`nix build .#options-doc`): the +# autogenerated `programs.trollshell.*` options reference (#533, phase 2 — +# see #540) plus the plugin env-knob reference (#573/#614) — to GitHub +# Pages. Deliberately a leaf: it is NOT part of `nix flake check` and +# doesn't gate any other workflow. The derivation itself renders every page +# (including the root landing page); this workflow only copies files out of +# `result/` into `_site/`. on: push: branches: [main] @@ -39,9 +42,12 @@ jobs: mkdir -p _site cp result/share/doc/trollshell/options.html _site/options.html cp result/share/doc/trollshell/options.md _site/options.md - # So the Pages root (https://.github.io/trollshell/) lands - # directly on the rendered doc instead of a 404. - cp result/share/doc/trollshell/options.html _site/index.html + cp result/share/doc/trollshell/plugin-env.html _site/plugin-env.html + cp result/share/doc/trollshell/plugin-env.md _site/plugin-env.md + # The derivation renders a small landing page linking both docs + # (#614); the Pages root (https://.github.io/trollshell/) + # serves that now instead of a byte-copy of options.html. + cp result/share/doc/trollshell/index.html _site/index.html - uses: actions/configure-pages@v5 - uses: actions/upload-pages-artifact@v5 with: diff --git a/flake.nix b/flake.nix index 001bcd8..fd8b717 100644 --- a/flake.nix +++ b/flake.nix @@ -125,20 +125,27 @@ description = "trollshell consent-gated agent-bridge broker CLI (#487)"; }; - # Autogenerated `programs.trollshell.*` options reference (#533). - # Evaluate BOTH platform modules together: they share the - # module-common base, deduped by its stable `key`, so the merged - # option tree is the full cross-platform + NixOS + home-manager - # surface in one place. `nixosOptionsDoc` renders it to CommonMark, - # which we also wrap into a standalone HTML page via `cmark` (tiny — - # no pandoc/Haskell closure). `_module.check = false` stops the config - # bodies (which set options owned by nixpkgs/home-manager that we do - # not declare here) from tripping the unknown-option check; the doc - # only forces option *metadata*, never the config, so nothing + # Autogenerated `programs.trollshell.*` options reference (#533), + # plus (#614) the hand-written per-plugin env-knob reference + # (`docs/plugin-env.md`, #573) rendered onto the same docs site so + # it stops being repo-only. Evaluate BOTH platform modules + # together: they share the module-common base, deduped by its + # stable `key`, so the merged option tree is the full + # cross-platform + NixOS + home-manager surface in one place. + # `nixosOptionsDoc` renders it to CommonMark; both it and + # `plugin-env.md` are wrapped into standalone HTML pages via the + # same `cmark-gfm` call (tiny — no pandoc/Haskell closure). The + # `-gfm` flavor over plain `cmark` is only so `plugin-env.md`'s + # pipe tables render as actual ``s — plain CommonMark has no + # table syntax and would print the `|` characters literally. + # `_module.check = false` stops the config bodies (which set + # options owned by nixpkgs/home-manager that we do not declare + # here) from tripping the unknown-option check; the doc only + # forces option *metadata*, never the config, so nothing # platform-specific is actually built. This is pure evaluation — it # does not compile the shell. Build + read with: # nix build .#options-doc - # $BROWSER result/share/doc/trollshell/options.html # or options.md + # $BROWSER result/share/doc/trollshell/index.html options-doc = let eval = pkgs.lib.evalModules { @@ -170,24 +177,71 @@ }) opt.declarations; }; }; + # The hand-written doc #614 is publishing. A plain repo-relative + # path (same style as the `callPackage ./nix/*.nix` calls + # above) — nix copies the single tracked file into the store, + # giving a store path just like `doc.optionsCommonMark` above. + pluginEnvMd = ./docs/plugin-env.md; in pkgs.runCommand "trollshell-options-doc" { - nativeBuildInputs = [ pkgs.cmark ]; - meta.description = "Autogenerated programs.trollshell options reference (#533)"; + nativeBuildInputs = [ pkgs.cmark-gfm ]; + meta.description = "Autogenerated programs.trollshell options reference (#533) + plugin env-knob reference (#573/#614)"; } '' mkdir -p "$out/share/doc/trollshell" cp ${doc.optionsCommonMark} "$out/share/doc/trollshell/options.md" - { + cp ${pluginEnvMd} "$out/share/doc/trollshell/plugin-env.md" + + # Shared page chrome so every page on the site reads as one + # document instead of ad hoc ones. + # `render_page TITLE MD_FILE [H1]` writes the finished HTML to + # stdout; --extension table is harmless on options.md (which + # has no pipe tables) so both pages go through the exact same + # cmark-gfm call. H1 is optional: options.md has no top-level + # heading of its own (nixosOptionsDoc starts straight at + # `##`), so it's injected here; plugin-env.md already opens + # with its own `# …` heading, so it's omitted for that page. + render_page() { + title="$1" + md="$2" + heading="$3" echo '' echo '' - echo 'trollshell — programs.trollshell options' - echo '' - echo '

programs.trollshell options

' - cmark --unsafe "$out/share/doc/trollshell/options.md" + echo "$title" + echo '' + echo '' + echo '' + if [ -n "$heading" ]; then + echo "

$heading

" + fi + cmark-gfm --unsafe --extension table "$md" echo '' - } > "$out/share/doc/trollshell/options.html" + } + + render_page 'trollshell — programs.trollshell options' \ + "$out/share/doc/trollshell/options.md" \ + 'programs.trollshell options' \ + > "$out/share/doc/trollshell/options.html" + + render_page 'trollshell — plugin environment-variable reference' \ + "$out/share/doc/trollshell/plugin-env.md" \ + "" \ + > "$out/share/doc/trollshell/plugin-env.html" + + # The root page (#614): a small landing page linking both + # documents, rather than a byte-copy of options.html now that + # there are two of them. + { + echo '' + echo '' + echo 'trollshell docs' + echo '' + echo '

trollshell docs

' + } > "$out/share/doc/trollshell/index.html" ''; in { From 6e6c4f55ba99a4a10ca7bbc0978f8101afe93ce7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 15:11:07 +0200 Subject: [PATCH 2/2] docs: gate options-doc in checks, fix link/style/copy gaps (#614 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adversarial review of PR #629 found four gaps, all one-liners on paper but one required a real structural fix: 1. options-doc was never wired into `checks`, so `nix flake check` (the only gate CI runs on PRs) never built the derivation this PR just made meaningfully bigger — the exact #449-class gap flake.nix already warns about for `trollshell`/`trollshell-control-center`. Wiring it in isn't a plain `inherit`, though: `options-doc` was defined inline inside `packages`'s own `let` block, which is a different scope from `checks`'s `let` block (each `forAllSystems` callback is independently scoped), so the name simply wasn't in reach. Extracted the whole derivation into nix/options-doc.nix — its own file, `pkgs.callPackage`'d from both `packages` and `checks` — matching how `trollshell`, `bundledPlugins`, and `hytte-infobroker` are already built in both outputs from a shared file rather than duplicated inline. `options-doc` is pure eval plus one tiny C binary, so this is effectively free on every `nix flake check`. 2. nix/module-common.nix's `plugins..env` option description — the only in-repo pointer to the env-knob reference — linked to raw Markdown on GitHub instead of the now-published plugin-env.html. Repointed to the absolute Pages URL (https://vibec0re.github.io/trollshell/plugin-env.html). Deliberately absolute, not relative: this same description is rendered by `nixos-option` and `man home-configuration.nix`, where a relative `plugin-env.html` resolves to nothing. 3. The landing page's inline ' - echo '' - echo '' - if [ -n "$heading" ]; then - echo "

$heading

" - fi - cmark-gfm --unsafe --extension table "$md" - echo '' - } - - render_page 'trollshell — programs.trollshell options' \ - "$out/share/doc/trollshell/options.md" \ - 'programs.trollshell options' \ - > "$out/share/doc/trollshell/options.html" - - render_page 'trollshell — plugin environment-variable reference' \ - "$out/share/doc/trollshell/plugin-env.md" \ - "" \ - > "$out/share/doc/trollshell/plugin-env.html" - - # The root page (#614): a small landing page linking both - # documents, rather than a byte-copy of options.html now that - # there are two of them. - { - echo '' - echo '' - echo 'trollshell docs' - echo '' - echo '

trollshell docs

' - } > "$out/share/doc/trollshell/index.html" - ''; + options-doc = pkgs.callPackage ./nix/options-doc.nix { inherit self pkgs; }; in { inherit @@ -354,14 +245,32 @@ { formatting = treefmt-eval.config.build.check self; + # `options-doc` (#533/#614): pure eval plus one tiny C binary + # (cmark-gfm) — no crane, nothing expensive — so there's no cost + # excuse for `options-doc.yml`'s actual render+copy pipeline to go + # unbuilt on every PR. Mirrors the `packages` output's binding (its + # own `let`, hence the separate `pkgs.callPackage` call rather than + # an `inherit` — same reason `trollshell` etc. below are + # recomputed here too instead of reused across outputs). + options-doc = pkgs.callPackage ./nix/options-doc.nix { inherit self pkgs; }; + # `nix flake check` only *builds* the derivations listed in `checks` # — it does not build `packages` just because they're evaluable. - # Without these two entries, CI (which runs flake check, not + # Without these entries, CI (which runs flake check, not # `nix build`) can stay green while `nix build .#trollshell` or # `.#trollshell-control-center` is actually broken — the release # profile, `nix/package.nix`'s src filter, the assets derivation, - # and the wrapper derivations are never exercised. See #449. - inherit trollshell trollshell-control-center hytte-infobroker; + # and the wrapper derivations are never exercised. See #449. Same + # argument extends to `options-doc` (#614, bound above — already + # in this attrset, so it isn't repeated in this `inherit`): without + # it here, a build break in it (a bad cmark-gfm flag, a quoting + # bug) stays green and first shows up as a red Pages deploy after + # merge. + inherit + trollshell + trollshell-control-center + hytte-infobroker + ; # Lint the entire workspace with pedantic-clean Clippy. Reuses # cargoArtifacts from the package build so dependencies aren't diff --git a/nix/module-common.nix b/nix/module-common.nix index c8ead78..269120d 100644 --- a/nix/module-common.nix +++ b/nix/module-common.nix @@ -326,8 +326,12 @@ self: instead of per-plugin here). The full inventory — swept from source, all 12 bundled - plugins including the ones with zero knobs — lives in - [`docs/plugin-env.md`](https://github.com/vibec0re/trollshell/blob/main/docs/plugin-env.md). + plugins including the ones with zero knobs — is published at + + (source: `docs/plugin-env.md`). An absolute URL rather than a + relative one because this description is also rendered by + `nixos-option` and `man home-configuration.nix`, where a + relative `plugin-env.html` link would resolve to nothing. Precedence for a plugin that also reads a config file (e.g. usage's `~/.config/trollshell/usage.toml`): environment (this diff --git a/nix/options-doc.nix b/nix/options-doc.nix new file mode 100644 index 0000000..6dba98c --- /dev/null +++ b/nix/options-doc.nix @@ -0,0 +1,139 @@ +# Autogenerated `programs.trollshell.*` options reference (#533), plus +# (#614) the hand-written per-plugin env-knob reference +# (`docs/plugin-env.md`, #573) rendered onto the same docs site so it stops +# being repo-only. Evaluate BOTH platform modules together: they share the +# module-common base, deduped by its stable `key`, so the merged option tree +# is the full cross-platform + NixOS + home-manager surface in one place. +# `nixosOptionsDoc` renders it to CommonMark; both it and `plugin-env.md` are +# wrapped into standalone HTML pages via the same `cmark-gfm` call (tiny — no +# pandoc/Haskell closure). The `-gfm` flavor over plain `cmark` is only so +# `plugin-env.md`'s pipe tables render as actual `
`s — plain +# CommonMark has no table syntax and would print the `|` characters +# literally. `_module.check = false` stops the config bodies (which set +# options owned by nixpkgs/home-manager that we do not declare here) from +# tripping the unknown-option check; the doc only forces option *metadata*, +# never the config, so nothing platform-specific is actually built. This is +# pure evaluation — it does not compile the shell. Build + read with: +# nix build .#options-doc +# $BROWSER result/share/doc/trollshell/index.html +# +# Its own file (rather than inlined per-output in flake.nix) so both the +# `packages` and `checks` outputs can `pkgs.callPackage` it without +# duplicating the derivation body — `checks` needs its own copy of this +# building (#614) for the same #449 reason `trollshell` et al. are +# recomputed in both outputs' `let` blocks rather than shared: each +# `forAllSystems` callback is its own scope, and `nix flake check` only +# *builds* what `checks` lists, not everything `packages` merely evaluates. +{ + pkgs, + # The flake's own `self`, threaded through so the module-eval below can + # `import` the two platform modules — not a `pkgs` attribute, so it has to + # be passed explicitly rather than auto-filled by `callPackage`. + self, +}: +let + eval = pkgs.lib.evalModules { + specialArgs = { inherit pkgs; }; + modules = [ + { _module.check = false; } + (import ./hm-module.nix self) + (import ./nixos-module.nix self) + ]; + }; + doc = pkgs.nixosOptionsDoc { + inherit (eval) options; + transformOptions = + opt: + opt + // { + # Document only the shell's own options — hide the module-system + # internals (`_module.*`) the bare evalModules introduces. Preserve + # the original visibility (true / "shallow") for our options; force + # everything else invisible. + visible = if pkgs.lib.hasPrefix "programs.trollshell" opt.name then opt.visible else false; + # Point "Declared by" at this repo. Our `_file` values are + # repo-relative paths (e.g. "nix/module-common.nix"); without this, + # nixosOptionsDoc assumes they live under nixpkgs and links to + # github.com/NixOS/nixpkgs, which is wrong. + declarations = map (decl: { + name = decl; + url = "https://github.com/vibec0re/trollshell/blob/main/${decl}"; + }) opt.declarations; + }; + }; + # The hand-written doc #614 is publishing. A plain repo-relative path (this + # file lives in nix/, so it's one level up from the repo root) — nix + # copies the single tracked file into the store, giving a store path just + # like `doc.optionsCommonMark` above. + pluginEnvMd = ../docs/plugin-env.md; +in +pkgs.runCommand "trollshell-options-doc" + { + nativeBuildInputs = [ pkgs.cmark-gfm ]; + meta.description = "Autogenerated programs.trollshell options reference (#533) + plugin env-knob reference (#573/#614)"; + } + '' + mkdir -p "$out/share/doc/trollshell" + cp ${doc.optionsCommonMark} "$out/share/doc/trollshell/options.md" + cp ${pluginEnvMd} "$out/share/doc/trollshell/plugin-env.md" + + # Shared page chrome — ONE style string for every page on the site + # (including the index landing page below) so a future tweak reaches all + # of them, rather than the landing page silently carrying its own + # divergent copy. + page_style='body{max-width:55rem;margin:2rem auto;padding:0 1rem;font-family:system-ui,sans-serif;line-height:1.5}code{background:rgba(127,127,127,.15);padding:.1em .3em;border-radius:3px}pre{overflow-x:auto}h1,h2,h3{line-height:1.2}table{border-collapse:collapse;width:100%}th,td{border:1px solid rgba(127,127,127,.35);padding:.4em .6em;text-align:left;vertical-align:top}nav.docs-nav{margin-bottom:1.5rem;font-size:.9em}li{margin:.5em 0}' + + page_head() { + echo '' + echo '' + echo "$1" + echo "" + echo '' + } + page_foot() { + echo '' + } + + # `render_page TITLE MD_FILE [H1]` writes the finished HTML to stdout; + # --extension table is harmless on options.md (which has no pipe + # tables) so both pages go through the exact same cmark-gfm call. H1 is + # optional: options.md has no top-level heading of its own + # (nixosOptionsDoc starts straight at `##`), so it's injected here; + # plugin-env.md already opens with its own `# …` heading, so it's + # omitted for that page. + render_page() { + title="$1" + md="$2" + heading="$3" + page_head "$title" + echo '' + if [ -n "$heading" ]; then + echo "

$heading

" + fi + cmark-gfm --unsafe --extension table "$md" + page_foot + } + + render_page 'trollshell — programs.trollshell options' \ + "$out/share/doc/trollshell/options.md" \ + 'programs.trollshell options' \ + > "$out/share/doc/trollshell/options.html" + + render_page 'trollshell — plugin environment-variable reference' \ + "$out/share/doc/trollshell/plugin-env.md" \ + "" \ + > "$out/share/doc/trollshell/plugin-env.html" + + # The root page (#614): a small landing page linking both documents, + # rather than a byte-copy of options.html now that there are two of + # them. Shares page_head/page_foot/page_style above rather than its own + # copy. + { + page_head 'trollshell docs' + echo '

trollshell docs

' + page_foot + } > "$out/share/doc/trollshell/index.html" + ''