A frozen spec is only meaningful if it's paired with the corpus
that was current at release time — otherwise a v0.1.0 reader
follows "see corpus/payloads/ack/" and lands on a file whose
test vectors may have changed since v0.1.0 was cut.
Three changes:
1. `cut-version` now copies BOTH src/ AND corpus/ into the
snapshot directory. A release is the pair, not just the text.
2. The corpus URL rewriting in cut-version now retargets
/tree/main/corpus -> /tree/main/versions/<v>/corpus (and the
blob variant). This lets the link resolve on GitHub from main
alone — no git tag required, since the frozen copy is a real
path under main. Downgrades the "tag is REQUIRED" message from
last commit back to "optionally tag the commit".
3. The frozen corpus's spec_ref fields get rewritten too: from
`src/04-payload-ack.md` to `versions/<v>/04-payload-ack.md`,
so a reader following a test vector lands on the spec text the
vector was paired with at release time.
versions/v0.1.0/ regenerated from scratch via `just cut-version v0.1.0`
to pick up the corpus snapshot + the new sed patterns. The release
is now self-consistent: v0.1.0 spec links go to v0.1.0 corpus, which
links back to v0.1.0 spec.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every "Test vectors: \`corpus/...\`" mention in the spec is now a
markdown link pointing at the corresponding file or directory on
GitHub. Each version build resolves its links to the correct git
ref:
- /latest/ -> https://github.com/swaits/meshcore-spec/tree/main/corpus/...
- /v0.1.0/ -> https://github.com/swaits/meshcore-spec/tree/v0.1.0/corpus/...
- future /vX.Y.Z/ -> /tree/vX.Y.Z/corpus/...
Two changes:
1. src/*.md (18 files): the inline-code corpus paths in each
"Cross-References" section are now markdown links. Directories
point at /tree/main/corpus/..., individual .json files point at
/blob/main/corpus/.... CHANGELOG and other non-corpus main links
are intentionally left pointing at main — the canonical history
lives there regardless of which version you're reading.
2. justfile: `cut-version` now also runs a sed over the snapshot's
*.md files replacing /tree/main/corpus -> /tree/<VERSION>/corpus
and /blob/main/corpus -> /blob/<VERSION>/corpus. So a freshly-cut
version is self-consistent.
versions/v0.1.0/* regenerated by re-running
`just cut-version v0.1.0` after the new src/ links + recipe sed
landed.
Tagging the release commit (`git tag v0.1.0`) is now REQUIRED — not
"optional" — because the rendered /v0.1.0/ pages reference
/tree/v0.1.0/corpus/... on GitHub. Without the tag the links 404.
cut-version's output now says so.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`just build` produces a single-version site to ./book/ with no
versions.json, and `just build-site` produces the multi-version site
but viewing it via file:// makes the browser block
fetch('versions.json') for security reasons. Both cases leave the
version-picker stuck in label-only mode locally — which looked like
the picker was broken when it was actually doing the right thing
given the available data.
`just preview` is the recipe to test the picker end-to-end:
1. Runs build-site (full _site/ with versions.json).
2. Serves _site/ at http://localhost:8000/ via python -m http.server.
3. Browser fetch() works, picker upgrades to a real dropdown,
navigation between /latest/ and /v0.X.Y/ works.
Also restructured all recipe doc-comments to single-line summaries
(just --list only shows the immediately-preceding comment, and the
old multi-line comments were rendering as confusing fragments).
Background commentary moved to a file-level header.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first version cut (v0.1.0) revealed a real gap: nothing in the
rendered book identified which release the reader was on. The
version-picker chip says "version: v0.1.0" when you're on /v0.1.0/,
but it's a small UI affordance — and on /latest/ it can't say
"v0.1.0" because /latest/ is the rolling main build, not the v0.1.0
release.
Two complementary fixes, both visible without JS:
1. **Body marker.** Top of every chapter-0 (overview) page now reads
`Spec version: <token>`. The token is `latest (main)` in src/, and
gets frozen to the actual version (`v0.1.0`) when cut-version
snapshots the directory. So /latest/ overview says "latest (main)"
and /v0.1.0/ overview says "v0.1.0".
2. **Per-build page title.** scripts/build-site.sh now sets
`MDBOOK_BOOK__TITLE` for each build:
- latest → "MeshCore Protocol Specification — latest (main)"
- vX.Y.Z → "MeshCore Protocol Specification — vX.Y.Z"
The browser tab and mdBook sidebar header reflect the version.
`just cut-version` updated to run a one-line sed on the snapshot's
00-overview.md replacing the `latest (main)` marker. Defensive grep
warns if the marker isn't found (e.g., src/ was edited away from
the expected line).
versions/v0.1.0/00-overview.md regenerated by re-running
`just cut-version v0.1.0` after the marker landed in src/ — so the
v0.1.0 snapshot now self-identifies correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the git-tag-based snapshot model with frozen-directory
snapshots. Each released spec version is its own subtree under
./versions/vX.Y.Z/ — a literal copy of ./src/ taken at release time.
Why: tagging the whole repo conflated *spec content* with *site
infrastructure* (book.toml, theme, picker JS, workflow, build
scripts). Improving any of those required cherry-picking the change
back to every old tag. With directory snapshots, content is frozen
but every deploy renders every version with TODAY's infrastructure
— site fixes propagate everywhere for free.
Changes:
- scripts/build-site.sh now iterates ./versions/v* directories
instead of `git tag --list 'v*'`. Each per-version build sets
MDBOOK_BOOK__SRC to the snapshot dir and MDBOOK_OUTPUT__HTML__SITE_URL
to /meshcore-spec/<version>/. The root book.toml, theme, and
picker JS apply uniformly.
- justfile gains `cut-version vX.Y.Z` — validates the name, refuses
to overwrite, runs `cp -R src versions/<version>`, prints next-
step hints. Cutting a release is now `just cut-version v0.1.0`,
then a commit; optionally tag the commit for provenance, but
tags no longer drive CI.
- .github/workflows/mdbook.yml drops the `tags: ['v*']` trigger
and `fetch-depth: 0` (no longer iterating git history). Triggers
on push to main and PRs.
- src/00-overview.md: "Spec Versions" section rewritten to describe
the directory-snapshot model and the deliberate content-vs-
infrastructure split.
Smoke-tested locally: `just build-site` with no versions/ produces
`_site/{latest/, versions.json=["latest"], index.html}`.
`just cut-version v0.0.1` + rebuild produces both `latest/` and
`v0.0.1/`, versions.json=`["latest","v0.0.1"]`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI now mirrors the local dev path. The workflow installs mise,
trusts the config, installs all mise-pinned tools (mdbook, just),
and invokes `just build-site` for the whole multi-version build.
There is no longer any build logic inside the workflow YAML.
Changes:
- `mise.toml` now also pins `just = "1.50.0"` so `mise install`
brings in both mdbook and just.
- `scripts/build-site.sh` (new, POSIX sh, shellcheck-clean) takes
the multi-version build loop out of the workflow and parks it
next to the rest of the build tooling. Iterates v* tags via
`git worktree`, sets `MDBOOK_OUTPUT__HTML__SITE_URL` per
version, emits `_site/versions.json` and `_site/index.html`
(root redirect to /latest/).
- `justfile` gains a `build-site` recipe — `sh scripts/build-site.sh`.
- `.github/workflows/mdbook.yml` is now four short steps inside
the build job: jdx/mise-action (install: false) → `mise trust`
→ `mise install` → `just build-site`. The previous inline
multi-line bash block is gone.
- `.gitignore` adds `/_site/` alongside the existing `/book/`.
Local smoke test: `just build-site` with no v* tags produces
`_site/{latest/, versions.json=["latest"], index.html}` exactly as
CI will. Future `v0.1.0` tag will add `_site/v0.1.0/` and update
`versions.json` without any code changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three intertwined changes — split conceptually but landed together
because they share the same workflow rewrite:
1. MULTI-VERSION SITE. The published site now keeps every prior spec
version alongside the rolling latest build (docs.rs / readthedocs
pattern):
- .github/workflows/mdbook.yml builds latest from current HEAD and
each v* tag in a git worktree, placing each into _site/<ver>/.
Each per-version build gets MDBOOK_OUTPUT__HTML__SITE_URL set to
/meshcore-spec/<ver>/ so the sitemap/canonical links resolve
correctly per version.
- Workflow emits _site/versions.json (newest first) and a root
index.html that redirects to /latest/.
- theme/version-picker.js is a small client-side dropdown that
fetches /<base>/versions.json and lets the reader switch to the
same chapter in any other version. Slots into mdbook's menu bar
(.right-buttons); falls back to a fixed top-right widget if the
menu isn't found.
- theme/version-picker.css styles it to fit the navy theme.
- book.toml wires the picker files via additional-js /
additional-css; site-url defaults to /meshcore-spec/latest/ for
the main build (CI overrides per tag).
- Old tags built without these files render without a picker, which
is fine — users won't typically need to navigate AWAY from an
older version. v0.1.0 onward will all carry the picker.
Spec versions use independent semver and do NOT mirror MeshCore
firmware versions; each spec release records the upstream commit
hash it was validated against in CHANGELOG.md.
2. MISE-PINNED TOOLS. mise.toml pins cargo:mdbook so local dev and
CI use the same version. The workflow installs everything via
jdx/mise-action instead of peaceiris/actions-mdbook, making
mise.toml the single source of truth for tool versions.
3. JUSTFILE. justfile gives short names (build / serve / clean /
validate / setup) for the common tasks. Recipes run under
`mise exec -- sh -c …` so they always pick up the mise-pinned
tools (sh is intentional, keeping recipes POSIX-compatible).
Documentation: README's spec link now mentions the versioning model;
src/00-overview.md gets a "Spec Versions" subsection explaining the
URL layout and version-picker.
Verified locally: `just build` produces a clean book/ with the
version-picker JS/CSS injected (hashed filenames, slotted into
theme/). JS parses clean under `node --check`.
Note for first-time deploy: GitHub Pages source must be set to
"GitHub Actions" in repo settings (one-time, manual). After that,
push to main builds + deploys latest, and pushing a v* tag adds
the snapshot to the site.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>