Skip to content

Give the docs one home, one checker, and a tree that matches the site - #103

Merged
MoonModules merged 10 commits into
mainfrom
docs-restructure
Sep 12, 2026
Merged

MoonModules merged 10 commits into
mainfrom
docs-restructure

Conversation

@ewowi

@ewowi ewowi commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

The documentation had the rules in one page, a second copy of them in two others, and no shape a reader could navigate. This branch gives the prose a single home, a checker that enforces it, and a folder tree that matches the published site.

350 files, but the reviewable part is small. 115 deletions and 103 renames account for most of it, which is why the diff is +3,200 / −14,460. Roughly 146,000 words removed.

The standards, and what enforces them

docs/contributing/documentation-standards.md is new: who reads a page, one tone of voice, the principles that bear on prose, then rules narrowing from the statement to the sentence to the word. coding-standards.md keeps pointers where its prose sections were, and gains one rule: prefer naming over commenting, with both sides cited.

Three things now hold it in place:

  • Diátaxis, followed as written. Every page is a tutorial, how-to, reference or explanation, and the test for a page is the cell it sits in.
  • Vale, seven rules as YAML under .vale/styles/projectMM/, replacing the hand-rolled checker's table. It runs on every PR and annotates errors on added lines.
  • --strict on the docs build, in CI and at the commit gate. A dead link fails the build. It caught 251 during the restructure, each fixed.

The folder tree is the type

Pages sat flat at the site root while the folders that existed carried mixed meanings: reference/ held hardware pages while the nav's "Reference" section held two unrelated ones. Each page now lives in the folder naming its type, and the nav follows the tree:

docs/
  index.md · gettingstarted.md   entry only
  tutorials/ · how-to/ · explanation/ · reference/
  contributing/ · legal/         outside the grid
  moonmodules/ · work/ · friend-repos/

Nav labels stay reader-facing ("Understanding projectMM", not "Explanation") because the type is a writer's tool. Published URLs changed and are not redirected; the MIGRATING entry lists every move.

Folder landing pages renamed README.mdindex.md, the MkDocs convention. Source-directory READMEs (mooncloud/, mooninstaller/, moonlive/) keep their name, which is what every code host renders.

The architecture is nine pages

architecture.md was one 15,000-word page. It is now nine under explanation/architecture/, one per Moon part, each opening with what the part is and how the page is laid out, with Mermaid diagrams replacing the ASCII ones. MoonLight's pipeline diagram draws Layouts as shared geometry read at prepare time rather than a pipeline stage, which is what the code does.

CLAUDE.md drops 5,143 → 3,130 words, with the gate tables redrawn as Mermaid flowcharts carrying role emoji. The README drops 3,486 → 2,008 and gains a section per Moon part, each linking both its architecture page and its user docs.

What was deleted, and how it was checked

The ADR folder, all 17 records. Each decision was already stated as current behavior elsewhere, so the ADR was the copy left behind. Two rules had no other home and moved into the architecture pages.

91 shipped plans, whose content the code, the tests and the merged PR carry. The check was inverted rather than trusted: does any sentence's vocabulary appear nowhere else in the repo, across all PR bodies plus every doc and source file? 18 sentences survived out of 131,562 words, and all 18 named code that no longer exists.

docs/history/ retired, its lessons and inventories moved under docs/work/past/.

Reviewer findings

The Reviewer ran over the full branch diff. Three fixed, two left as decisions:

  • repo_health.py read a path that no longer existed. Guarded by if exists(), so it degraded to lessons_lines: 0 rather than erroring, and that zero was already committed to the KPI file. Fixed; it reads 518.
  • Fourteen files linked ](README.md) for a sibling renamed to index.md. The out-of-docs hook matched the bare string and rewrote them to projectMM's root README, so they resolved to the wrong document with no error and a green build.
  • ~20 stale doc paths in src/, moondeck/, CMakeLists.txt, mkdocs.yml and esp32/, left by the earlier docs/history/ and docs/backlog/ retirements.
  • Open: whether work/future/ should inherit the old docs/backlog/ prose exemption, and whether two MoonLive module pages should be on the nav.

Verification

Strict docs build 0 warnings. Prose clean over the branch diff. check_specs 126/126, taglines agree, platform boundary and hot-path pass. Desktop build zero warnings, 1,938 unit cases / 123,412 assertions, 24 scenarios, host tests 170 Python + JS. GCC skipped (CI green), ESP32 and Improv skipped (no board, and the branch does not touch that path).

🤖 Generated with Claude Code

Documentation rules now live in their own page instead of half a section inside the coding standards, and the three places that were carrying a second copy of a rule got pruned: the ADR folder is gone, the lessons log lost its branch diaries, and the plan archive lost the 90 plans whose content exists elsewhere. Roughly 146,000 words removed, and every rule that was worth keeping moved to where it fires.

Docs/CI:
- docs/documentation-standards.md is new: who reads a page, one tone of voice, the principles that bear on prose, then rules from the statement down to the word. Present tense and positive form nest under Minimalism, where CLAUDE.md states them.
- coding-standards.md drops from 261 to 204 lines, keeping pointers where the prose and documentation-model sections were.
- CLAUDE.md stops restating the prose rules and links them instead. Principle 5 is renamed "Continuous improvement", because the old title said sweep the repo while its own sub-rules said the opposite. Minimalism gains "the simple solution is the one to find, not the one to settle for", and Roles gains three guards against agent drift, of which one is checkable: an addition names its subtraction.
- docs/work/{past,present,future} replaces docs/backlog and docs/history/plans. Future is what does not exist yet, present is being built, past is what shipped. Each gets a README, and past carries the plan-to-PR mapping table.
- The ADR folder is deleted, all 17 records. Each decision was already stated as current behavior in architecture.md or the standards, so the ADR was the copy that stayed behind. Two rules had no other home and moved into architecture.md: MoonLive's expressions-plus-host-bound-functions model, and SET_BOARD carrying only the board name.
- lessons.md drops 4,781 words of branch diaries that state their own graduation ("Now CLAUDE.md", "the general rule lives in coding-standards.md"). Three method lessons had no rule-doc home and moved: two test-verification rules into testing.md, the hardware-first debugging rule into CLAUDE.md.
- 90 pre-August plans deleted. The check was not overlap with their PR, which found almost nothing: it was whether any sentence's vocabulary appears nowhere else in the repo, across all 79 PR bodies plus every doc and source file. 18 sentences survived that filter out of 131,562 words, and all 18 name code that no longer exists.
- A new backlog entry: read a vehicle's CAN bus and drive lights from it, read-only, as a Service rather than a driver.

Core:
- The nine ADR citations in src/ became the facts they pointed at, so a comment no longer sends a reader to a file that is gone.

Tests:
- Three ADR citations in test comments replaced the same way.

Verified: MkDocs strict builds with zero warnings (it caught 153 broken links during the restructure, each fixed), prose clean, check_specs 126/126, check_taglines agree, zero dangling plan links.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: ab20f4a7-b776-4cd2-81df-6d488cb4ef34

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MoonModules

Copy link
Copy Markdown
Owner

@coderabbitai, can you review the modified (38) and added (5) file of commit 5e3fff7

No need to review deleted (112) or moved files (77)

@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

commit="5e3fff795af41e9ad016166b83e35d1710d76f27"

echo "== Commit metadata =="
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$commit"

echo
echo "== Changed files by status =="
git diff-tree --no-commit-id --name-status -r -M -C "$commit"

echo
echo "== Requested files: added and modified =="
git diff-tree --no-commit-id --name-status -r -M -C "$commit" \
  | awk '$1 == "A" || $1 == "M" {print}'

echo
echo "== Requested-files diff stat =="
parent="$(git rev-parse "${commit}^")"
git diff --stat "$parent" "$commit" -- \
  $(git diff-tree --no-commit-id --name-status -r -M -C "$commit" \
    | awk '$1 == "A" || $1 == "M" {print $2}')

Length of output: 26720


🏁 Script executed:

#!/bin/bash
set -euo pipefail

commit="5e3fff795af41e9ad016166b83e35d1710d76f27"
parent="$(git rev-parse "${commit}^")"
mapfile -t requested < <(
  git diff-tree --no-commit-id --name-status -r -M -C "$commit" |
  awk '$1 == "A" || $1 == "M" {print $2}'
)

echo "== Full diffs for the 43 requested files =="
git diff --no-ext-diff --unified=35 "$parent" "$commit" -- "${requested[@]}"

echo
echo "== Current non-deleted references to legacy documentation paths =="
rg -n -i -C2 \
  '(docs/)?backlog/|docs/history/plans/|/backlog/|/history/plans/' \
  -g '!docs/backlog/**' -g '!docs/history/plans/**' -g '!**/.git/**' . || true

echo
echo "== Changed validation-script context =="
for file in moondeck/check/check_prose.py moondeck/check/repo_health.py moondeck/rename/rename_to_moonlight.py; do
  echo "--- $file ---"
  ast-grep outline "$file" --items all
done

echo
echo "== MkDocs navigation and work-folder documentation =="
sed -n '/^nav:/,/^[^[:space:]-].*:/p' mkdocs.yml
for file in docs/work/future/README.md docs/work/present/README.md docs/work/past/README.md docs/work/past/plans/README.md; do
  echo "--- $file ---"
  cat -n "$file"
done

echo
echo "== Markdown links in requested documentation that resolve to missing local files =="
python3 - "${requested[@]}" <<'PY'
import pathlib, re, sys
files = [pathlib.Path(p) for p in sys.argv[1:] if p.endswith(".md")]
link = re.compile(r'(?<!!)\[[^\]]*\]\(([^)\s]+)(?:\s+["\'][^)]*["\'])?\)')
for path in files:
    text = path.read_text(encoding="utf-8")
    for target in link.findall(text):
        target = target.strip("<>")
        if target.startswith(("#", "http://", "https://", "mailto:", "tel:")):
            continue
        target = target.split("#", 1)[0].split("?", 1)[0]
        if not target:
            continue
        resolved = (path.parent / target).resolve()
        if not resolved.exists():
            print(f"{path}: missing target {target}")
PY

Length of output: 2338


🏁 Script executed:

#!/bin/bash
set -euo pipefail

commit="5e3fff795af41e9ad016166b83e35d1710d76f27"
parent="$(git rev-parse "${commit}^")"
requested_file="$(mktemp)"
trap 'rm -f "$requested_file"' EXIT

git diff-tree --no-commit-id --name-status -r -M -C "$commit" |
  awk '$1 == "A" || $1 == "M" {print $2}' > "$requested_file"

echo "== Full diffs for the 43 requested files =="
git diff --no-ext-diff --unified=35 "$parent" "$commit" -- $(cat "$requested_file")

echo
echo "== Current non-deleted references to legacy documentation paths =="
rg -n -i -C2 \
  '(docs/)?backlog/|docs/history/plans/|/backlog/|/history/plans/' \
  -g '!docs/backlog/**' -g '!docs/history/plans/**' -g '!**/.git/**' . || true

echo
echo "== Changed validation-script context =="
for file in moondeck/check/check_prose.py moondeck/check/repo_health.py moondeck/rename/rename_to_moonlight.py; do
  echo "--- $file ---"
  ast-grep outline "$file" --items all
done

echo
echo "== MkDocs navigation and work-folder documentation =="
sed -n '/^nav:/,/^[^[:space:]-].*:/p' mkdocs.yml
for file in docs/work/future/README.md docs/work/present/README.md docs/work/past/README.md docs/work/past/plans/README.md; do
  echo "--- $file ---"
  cat -n "$file"
done

echo
echo "== Markdown links in requested documentation that resolve to missing local files =="
python3 - "$requested_file" <<'PY'
import pathlib, re, sys
files = [pathlib.Path(p) for p in pathlib.Path(sys.argv[1]).read_text().splitlines() if p.endswith(".md")]
link = re.compile(r'(?<!!)\[[^\]]*\]\(([^)\s]+)(?:\s+["\'][^)]*["\'])?\)')
for path in files:
    for target in link.findall(path.read_text(encoding="utf-8")):
        target = target.strip("<>")
        if target.startswith(("#", "http://", "https://", "mailto:", "tel:")):
            continue
        target = target.split("#", 1)[0].split("?", 1)[0]
        if target and not (path.parent / target).exists():
            print(f"{path}: missing target {target}")
PY

Length of output: 192109


🏁 Script executed:

#!/bin/bash
set -euo pipefail

commit="5e3fff795af41e9ad016166b83e35d1710d76f27"
parent="$(git rev-parse "${commit}^")"

echo "== Compact diffs: documentation and configuration =="
git diff --no-ext-diff --unified=5 "$parent" "$commit" -- \
  CLAUDE.md \
  docs/MIGRATING.md docs/architecture.md docs/building.md docs/coding-standards.md \
  docs/documentation-standards.md docs/history/README.md docs/history/lessons.md \
  docs/history/shift-register-driver-analysis.md docs/moonmodules/core/system.md \
  docs/moonmodules/light/MoonLiveEffect.md docs/moonmodules/light/drivers.md \
  docs/moonmodules/light/effects.md docs/moonmodules/light/layouts.md \
  docs/moonmodules/light/modifiers.md docs/moonmodules/light/power-functions.md \
  docs/performance.md docs/reference/control-surfaces.md docs/reference/gpio-usage.md \
  docs/reference/light-fixtures.md docs/reference/mhc-wled-esp32-p4-shield.md \
  docs/testing.md docs/usecases/home-automation.md \
  docs/work/future/README.md docs/work/present/README.md docs/work/past/README.md \
  docs/work/past/plans/README.md mkdocs.yml

echo
echo "== Compact diffs: scripts, source, and tests =="
git diff --no-ext-diff --unified=5 "$parent" "$commit" -- \
  moondeck/check/check_prose.py moondeck/check/repo_health.py \
  moondeck/rename/rename_to_moonlight.py src/core/DevicePlugin.h \
  src/core/DevicesModule.h src/core/FilesystemModule.cpp \
  src/light/drivers/MoonLedDriver.h src/platform/desktop/platform_desktop.cpp \
  src/platform/esp32/platform_esp32.cpp src/platform/esp32/platform_esp32_i80.cpp \
  src/platform/esp32/platform_esp32_moon_i80.cpp src/platform/platform.h \
  test/unit/core/unit_InstallationId.cpp test/unit/light/unit_Effects_gridsweep.cpp \
  test/unit/light/unit_MoonLedDriver.cpp

echo
echo "== Added-line prose-rule violations under the updated checker =="
python3 - "$parent" <<'PY'
import re, subprocess, sys
base = sys.argv[1]
suffixes = (".h", ".hpp", ".c", ".cpp", ".inc", ".md", ".py", ".js", ".css", ".html", ".mle", ".mll", ".mlm")
exempt = (
    "docs/friend-repos/", "docs/work/past/", "docs/work/future/", "docs/metrics/",
    "docs/tests/", "docs/moonmodules/", "src/platform/desktop/vendor/", "src/ui/vendor/",
    "moondeck/check/check_prose.py", "docs/documentation-standards.md",
)
spelling = {
    "behaviour": "behavior", "colour": "color", "initialis": "initializ",
    "optimis": "optimiz", "recognis": "recogniz", "analyse": "analyze",
    "analysing": "analyzing", "materialis": "materializ", "normalis": "normaliz",
    "serialis": "serializ", "cancelled": "canceled", "modelling": "modeling",
    "labelled": "labeled", "centre": "center", "licence": "license", "defence": "defense",
}
diff = subprocess.run(["git", "diff", base, "--unified=0"], text=True, capture_output=True, check=True).stdout
path = None
for line in diff.splitlines():
    if line.startswith("+++ b/"):
        path = line[6:]
    elif line.startswith("+") and not line.startswith("+++") and path:
        text = line[1:]
        if path.endswith(suffixes) and not any(path == x or path.startswith(x) for x in exempt):
            issues = []
            if "\u2014" in text: issues.append("em-dash")
            lower = text.lower()
            issues += [f"{bad}->{good}" for bad, good in spelling.items() if bad in lower]
            if issues:
                print(f"{path}: {', '.join(issues)}: {text}")
PY

echo
echo "== Active legacy-path links in requested Markdown files =="
python3 - "$commit" <<'PY'
import pathlib, re, subprocess, sys, urllib.parse
commit = sys.argv[1]
raw = subprocess.run(
    ["git", "diff-tree", "--no-commit-id", "--name-status", "-r", "-M", "-C", commit],
    text=True, capture_output=True, check=True).stdout
files = [line.split("\t", 1)[1] for line in raw.splitlines()
         if line.startswith(("A\t", "M\t")) and line.endswith(".md")]
link = re.compile(r'(?<!!)\[[^\]]+\]\(([^)\s]+)')
legacy = re.compile(r'(?:^|/)(?:docs/)?(?:backlog|history/plans)(?:/|$)')
for name in files:
    path = pathlib.Path(name)
    for lineno, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
        for target in link.findall(line):
            decoded = urllib.parse.unquote(target.strip("<>").split("#", 1)[0].split("?", 1)[0])
            if legacy.search(decoded):
                print(f"{name}:{lineno}: {target}")
PY

Length of output: 203900


🏁 Script executed:

#!/bin/bash
set -euo pipefail

commit="5e3fff795af41e9ad016166b83e35d1710d76f27"

echo "== CLAUDE.md documentation inventory at the reviewed commit =="
git show "$commit:CLAUDE.md" | nl -ba | sed -n '249,271p'

echo
echo "== History plan-archive statement at the reviewed commit =="
git show "$commit:docs/history/README.md" | nl -ba | sed -n '14,27p'

echo
echo "== Retained shipped plan files at the reviewed commit =="
git ls-tree -r --name-only "$commit" -- docs/work/past/plans \
  | awk '$0 !~ /\/README\.md$/ {count++; print} END {print "COUNT=" count}'

Length of output: 6305


@MoonModules I reviewed only the 38 modified files and 5 added files in commit 5e3fff795af41e9ad016166b83e35d1710d76f27.

I found two issues:

  1. CLAUDE.md Lines 259-260 still link to backlog/index.html and adr/index.html. This commit removes both documentation locations. Replace these entries with the new work/future location and remove or replace the ADR entry. Otherwise, the published documentation inventory contains dead links.

  2. docs/history/README.md Line 23 says plans/ holds 89 plans. The new docs/work/past/plans/ directory contains 18 retained plan files, and docs/work/past/README.md also states 18. Update the history index to match the retained archive.

The added-line prose check reported no violations.

You are interacting with an AI system.

ewowi and others added 8 commits September 12, 2026 11:00
…s build

Writing rules are only as good as what holds them after the session ends. Three things now do: every page is one Diátaxis type, Vale checks the prose on every PR against the rules as YAML, and a dead link fails the docs build. The first three pages are swept clean and held strict, so a regression on them fails the gate instead of annotating a review.

Docs/CI:
- documentation-standards.md gains what the review found missing: a rule states a test, one example only where prose misreads, a link's text names what it reaches, the text never refers to itself, ask rather than argue, one thought per sentence. The last three came from two reviewers naming the same defect in different texts: prose that performs diligence instead of doing the job. Its Writing list is reordered from page down to word, its four duplicate rules merged, and it names its reader.
- What we document is the Diátaxis grid, followed as written: tutorial, how-to, reference, explanation, and a page is one of them. The nav splits how-to guides from tutorials, moves MoonCloud and the privacy policy under Reference, and the firmware variant list, scaling table and degradation cascade leave architecture.md for a reference page of their own. architecture.md itself stays explanation and is reserved for its own rework.
- Vale replaces the hand-rolled rule table. Seven rules under .vale/styles/projectMM/, one file each, including the three the reviewers named. .github/workflows/prose.yml runs it on every PR, diff-scoped, failing on error and annotating the rest. Three finished pages are held to every rule on the whole file.
- The docs build is --strict in CI and in the commit gate. It caught 153 dead anchors during the folder restructure; its rationale for being off had gone stale.
- coding-standards.md read as one path, from the line being typed out to the tooling, at 3,781 words from 4,547: 46 em-dashes out, six British spellings, the clangd setup moved to building.md where once-per-machine steps live, an include-cycle walkthrough that duplicated the header's own comment cut, four negations recast in positive form.
- The Linux tutorial at 917 words from 1,680, with the five self-references a reviewer flagged gone and a stale "Docker in development" note fixed. A vocabulary diff against the old page found four facts dropped on the first pass; all four are back at the step where each applies.
- A sweep plan under docs/work/present/ states the gate (whole-file Vale clean everywhere, then the two prose scripts go), the method, and a 55-page worklist generated from Vale so it cannot drift.
- Two fixes the rabbit found on #103: CLAUDE.md's documentation inventory linked to the deleted backlog/ and adr/ paths, and history/README.md claimed 89 plans where 18 remain.

Scripts/MoonDeck:
- check_prose.py owns scope and nothing else: it walks the diff and feeds Vale the added lines, and checks finished pages whole. Two bugs found by control-checking it: the exit code keyed on a severity word that Vale's line output does not carry, and added lines joined by one newline merged into a paragraph, flagging a 40-word sentence that existed nowhere. Both scripts carry their retirement condition in their docstring.

Verified: strict docs build 0 warnings, prose gate 0 errors with a planted regression exiting 1, 170 Python tests, specs 126/126, taglines agree.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The README opened with 1,627 words before the first instruction. It now opens with how to flash a board, and every capability links to the page that owns its detail instead of restating it. The documentation standards gained the rule that makes that checkable: a page links down, it does not absorb.

Docs/CI:
- README.md drops from 3,486 to 1,364 words, 1,627 to 551 before "Getting started". Fifteen feature bullets become eleven, each a capability plus a link; the Arduino, third-party-library and MoonBase rationale paragraphs become links to building.md, why-we-write-our-own.md and architecture.md. Two performance tables become one 128x128 row set plus a pointer to performance.md. Credits and History stay, trimmed, because attribution is the README's own. The hero is now a light wall rendering ColorTrails rather than a grey UI screenshot.
- documentation-standards.md gains "The hierarchy" directly after the intro: a tree from README down through the section pages, the rules, the module rows, the generated pages, to the .h files, with each branch labelled by its Diatax
is type. This restores the half of "document once, and generate or link the rest" that the split in 2457c5a dropped: the one-home half survived, the link-down half did not, and nothing said so at the time.
- installing-to-desktop.md now carries the macOS and Linux first-run steps it had been pointing at the README for, including the Gatekeeper quarantine fix, which the README cut would otherwise have left with no home anywhere in the tree.
- prose.yml checks out full history. The Vale job failed on "too many results in diff": with fetch-depth 1 reviewdog has no base commit, so filter_mode: added treats all 61 files as new and the run dies on GitHub's 50-annotation cap. Every finding was a warning; none could have failed the job on merit.
- .vale.ini exempts docs/work/present, the way past and future already were. A plan there is a working document that ends at its PR.
- lessons.md loses a duplicated clause and a weasel word in a line this branch wrote.

Verified: prose clean, strict docs build zero warnings, check_specs 126/126, taglines agree. A vocabulary diff of the old and new README flagged every dropped term and each substantive one was traced to an owning page; the one with no owner became the tutorial addition above.

Performance: desktop 7,194 -> 7,812 fps (tick 139 -> 128 us), host measurement noise on a docs-only diff. docs.md_lines +27 counts docs/ only, where this commit is net +42; the README's -65 lines sit outside that metric. loc.moondeck +21 is baseline drift matching moondeck/ changes already committed in 2457c5a.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Vale job failed on prose nobody in the PR wrote. It now annotates the lines a PR adds and blocks nothing, leaving check_prose.py as the gate, which is the one that scopes to added lines.

Docs/CI:
- prose.yml drops fail_on_error. Raising fetch-depth to 0 fixed the annotation cap, cutting reported findings from about 3,100 to 45, but the job still failed: filter_mode scopes what reviewdog REPORTS, while fail_on_error judges everything Vale emitted, and Vale reads the whole tree. That is about 1,900 errors across 39 files of prose predating the standards, so the job failed on every PR whatever the PR changed. The MIGRATING.md findings were the proof: line 3 and line 238 are unchanged on main, while the three lines this branch adds there are clean.
- The two comments beside it were wrong and are rewritten. One claimed filter_mode applies the same scope as check_prose.py, which is the assumption that produced the bug. The condition for turning fail_on_error back on is now written where it is read: once the sweep clears the tree.
- The sweep tracker said the job fails on error. It no longer does, so the line says what the job does today and which check actually blocks.

The previous commit's message carried "Diataxis" split across a line break. The word is spelled correctly here.

Verified: workflow YAML parses, fail_on_error absent, prose gate exits 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Vale job posts its annotations and reports neutral instead of failed, so the output is there to read and act on without a red X that no PR can clear.

Docs/CI:
- prose.yml marks the job continue-on-error. Vale lints whole files, so it sees the roughly 1,600 findings carried by prose that predates the standards, and no action input narrows it to the diff: filter_mode scopes which findings become annotations, never which files Vale reads. Measured on this branch, the 91 changed markdown files hold 1,085 errors, 49 warnings and 434 suggestions, none of them on lines this branch wrote.
- level is set back to error. Dropping fail_on_error also dropped the level to its info default, which spent GitHub's 50-annotation budget on suggestions. Errors are what the budget should buy.
- The fetch-depth comment blamed the annotation cap on the shallow clone. Measurement says otherwise: the cap follows the volume of findings, and full history is needed for a separate reason, that reviewdog identifies added lines by diffing against the base commit.
- The sweep tracker records the neutral report and the measured figure, so the page says what the job does today.

check_prose.py remains the gate that blocks, on added lines alone, where it exits 0. Whole-file Vale becomes possible on the day the sweep clears the tree, which is the condition the tracker already states.

Verified: workflow YAML parses, continue-on-error set, prose gate exits 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Vale annotations post and nothing goes red. The v2 action published its own check run, which stayed failed however the job exited, so the workflow said success while the commit still showed a failing check.

Docs/CI:
- prose.yml moves to vale-cli/vale-action, pinned to the v3.0.0 commit 518a9136. Three v2 limits go away. It reads the level input, where v2 derived reviewdog's -level from fail_on_error and pinned it to info, spending the annotation budget on suggestions. It offers fail_level, where none means report and never block, so continue-on-error comes back out. And it defaults to a reporter that writes annotations through the runner log rather than creating a check run, which is what published the failure.
- reporter is github-pr-annotations for that reason. The check-run reporters (github-pr-check, github-check) publish a status of their own that a job-level setting cannot reach.
- v3 installs reviewdog 0.21.0 by default, which is the version fail_level needs. Below it the input silently falls back to fail_on_error.
- The sweep tracker describes the new shape: annotates errors on added lines, blocks nothing, and names fail_level as the switch.

check_prose.py remains the gate that blocks, on added lines alone, where it exits 0.

Verified: workflow YAML parses, continue-on-error gone, the action pinned by commit SHA, prose gate exits 0. The v3 behavior is read from its action.yml and main.ts rather than assumed, since the same inputs on v2 were declared and ignored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The history folder is gone. Its four documents moved to where the work they describe already lives, and the README gained a section per Moon component with a diagram tying them together.

Docs/CI:
- docs/history/ is retired. The orphan test decided the shape: nothing was deleted, because nothing was redundant. The three LED-driver analyses carry 331, 237 and 55 words found nowhere else in the repo, competitor internals and rejected-design vocabulary that no shipped code records, so they moved to work/future/ beside the twelve analyses already there, which is what they call themselves in their own first line. lessons.md is unique in 39 of its 41 sections and moved to work/past/, beside the inventories and release notes. The README's cross-repo trends moved to friend-repos/, where every link in them points, and its inventory list to work/past/README.md.
- 43 references repointed across 23 files, plus five that were already stale before this change: two cite history/decisions.md, deleted some time ago, and three cite docs/history/plans/, which moved to work/past/plans/ and never took its citations with it.
- CLAUDE.md drops its four references to the folder, and the merge gate stops offering a new ADR as a destination for a decision, since that folder went in an earlier commit.
- 37 British spellings become American, which is the durable half of the prose sweep: a rewrite preserves a word, so these survive the cleanup the em-dashes will not. Rewriting those lines made this change the author of fifteen em-dashes already on them, fixed here by reading each one rather than substituting a comma everywhere.

The README gains a section per part:
- Nine sections, MoonModule first, each with a description, a link to the page that owns its detail, and an image where one exists. A Mermaid diagram above them shows MoonCore under MoonModule, the device parts in one box, and MoonInstaller, MoonDeck and MoonCloud reaching in over USB, the network and an opt-in connection.
- MoonCore and MoonLight are new names for src/core and src/light, introduced here rather than in the code. MoonLight also names the whole project after the planned rename, so the word will mean two things until that is settled.
- The installer and MoonDeck images move out of Getting started, which keeps the instructions and links up to the sections that now carry the pictures.

Verified: strict docs build zero warnings (it caught 21 broken links when the first repoint pass rewrote relative paths inside the moved files), prose gate zero errors, check_specs 126/126, taglines agree, and the Mermaid source renders through the real mermaid-cli rather than by inspection. The seven changed source files are comment-only, checked line by line rather than assumed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rulebook every session loads is 5,416 words down to 3,011, and the lifecycle is five Mermaid diagrams rather than two tables and a wall of prose. Nine documentation pages are Vale-clean.

Docs/CI:
- CLAUDE.md loses 2,405 words, about 3,600 tokens on every session. Three kinds of cut: the war stories that proved a rule once and then cost tokens forever, rationale that building.md and MoonDeck.md already own, and the same product-owner rule stated in three sections. A vocabulary diff over the old text found only two sentences with no counterpart in the new one, and three rules it did drop came back: archiving a plan is the PO's call, the spec-draft lifecycle, and why the GCC build exists.
- Five diagrams replace the tables and the prose around them: the lifecycle, pick-spec-plan, build and test, the commit gates, the merge gates. The commit table was seventeen rows of check, command and trigger; the triggers collapse into six categories and the commands live in MoonDeck.md, one section per script, so the diagram carries what the table did in fewer tokens. Each one renders through mermaid-cli rather than by inspection, and each sits between 459 and 1,025 pixels wide so a phone can read it.
- classDef replaces per-node style lines. Four classes named for their role, po, agent, check and gate, so the palette's meaning is in the source rather than only in the prose. Identical rendering, 150 tokens less. A classDef cannot cross a fence: each diagram declares its own, and an undefined class fails silently rather than erroring.
- collect_kpi moves off "always" onto the code branch. This commit is the evidence: a docs-only diff recorded desktop tick 128 to 132 microseconds and 7,812 to 7,575 fps, two warning flags that nothing in the diff caused. Its timings drift with the host, so it belongs where the code it measures changes.
- mkdocs.yml gains a mermaid custom fence, so a diagram renders on the site rather than as a code block.

Docs:
- Nine pages go Vale-clean: index, mooncloud, privacy-policy, control-surface, core/supporting, light-fixtures, why-we-write-our-own, led-signal-integrity and MoonLiveModifier. 34 fixes, each read rather than substituted: an em-dash becomes a comma, a colon or a full stop by what the sentence is doing, and a negated heading is renamed for what its section is.
- documentation-standards.md draws its hierarchy as a diagram and gains a rule: a diagram beats the paragraph that describes it. Purple is the four Diataxis types, green is generated from source, gold sits outside the grid, and the intro says so, because a colour scheme only the author knows is decoration.
- The Improv provisioning path is a link now. MoonDeck.md already lists those five files, each annotated with what it is.

Verified: prose gate zero errors, strict docs build zero warnings, check_specs 126/126, taglines agree, and all five diagrams render.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The documentation site's pages sat flat at the root while the folders that did
exist carried mixed meanings, so a path said nothing about what a page was.
Every page now lives in the folder naming its type, the menu follows the tree,
and the architecture explanation is nine pages instead of one.

**Docs/CI**
- Split `architecture.md` into nine pages under `explanation/architecture/`, one per Moon part, each with its own intro, layout paragraph and Mermaid diagrams
- Move every page into its Diátaxis folder: `tutorials/`, `how-to/`, `explanation/`, `reference/` (with `hardware/`, `metrics/`, `tests/`), plus `contributing/` and `legal/` for what sits outside the grid; only `index.md` and `gettingstarted.md` stay at the root
- Rename five folder landing pages from `README.md` to `index.md`, the MkDocs convention; source-directory READMEs (`mooncloud/`, `mooninstaller/`, `moonlive/`) keep their name, which is the convention every code host renders
- Rewrite the nav to match the tree, with reader-facing labels over type jargon
- Replace MoonLight's ASCII pipeline with a Mermaid diagram, drawing Layouts as shared geometry read at prepare time rather than a pipeline stage
- Pair architecture and user-doc links in all nine README Moon sections
- Update `documentation-standards.md`'s hierarchy diagram and Diátaxis table, and state the rule the layout now follows: the folder is the type
- Add the MIGRATING entry for the changed published URLs, which are not redirected

**Scripts/MoonDeck**
- Repoint the hardcoded doc paths in `repo_health.py`, `check_lizard.py`, `check_nonblocking.py` and `generate_test_docs.py`, which would otherwise have written to folders that no longer exist
- Fix `mkdocs_hooks.py`'s `_EMBEDS_REPO_ROOT_FILE` key and test-page staging URIs after the moves
- Update `.gitignore` for the relocated generated test pages

**Core · Light domain · Tests**
- Update the doc paths cited in source comments; no code changed

Pre-commit: prose reports 4 em-dash errors, all pre-existing prose in pages this
change only moved (157 across those four files on main). They surface because the
check diffs added lines and a rename reads as new; the documentation sweep owns
them. Repo health not run: every src/ and test/ change is a comment string.
Improv smoke test skipped, no board attached and the diff misses that path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ewowi ewowi changed the title Split the documentation standards out, and cut what duplicates them Give the docs one home, one checker, and a tree that matches the site Sep 12, 2026
The restructure moved pages but left ~40 references behind in source comments,
build files and sibling links. One of them was silently corrupting a recorded
metric. Found by the pre-merge Reviewer over the branch diff.

**Scripts/MoonDeck**
- `repo_health.py` read `docs/history/lessons.md`, gone since that folder was retired. The read is guarded by `if exists()`, so it degraded to `lessons_lines: 0` instead of erroring, and that zero was already committed to the KPI file. It reads 518 again
- Drop the `docs/work/future/` prose exemption: its comment described the old `docs/backlog/` and no longer applied. The folder is already Vale-clean across 28 files, so it costs nothing now and catches drift from here
- Remove citations of two plans that no longer exist, and the stale "Phase 0, zero content changes" framing that the restructure ended

**Docs/CI**
- Fourteen files linked `](README.md)` for a sibling renamed to `index.md`. The out-of-docs hook matched the bare string and rewrote them to projectMM's root README, so they resolved to the wrong document with a green build and no 404
- Put `MoonLiveLayout` and `MoonLiveModifier` on the nav beside the effect page; they were reachable only by link
- Repoint the doc paths in `CMakeLists.txt`, `esp32/main/CMakeLists.txt` and `mkdocs.yml`

**Core · Light domain · UI**
- Repoint `docs/history/`, `docs/backlog/` and `docs/architecture.md` references in source comments; no code changed

Pre-merge: prose clean over the branch diff, docs build 0 warnings, specs 126/126,
taglines agree, boundary and hot-path pass, desktop build zero warnings, 1938 unit
cases, 24 scenarios, host tests green. GCC skipped, CI is green on ab33f29. ESP32
and Improv skipped: no board, and the branch does not touch that path.

Scenario observations ride along unchanged in content: the run rotated their sample
windows, but no code changed, so there is nothing in this diff to explain a move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MoonModules
MoonModules merged commit ee4b348 into main Sep 12, 2026
7 checks passed
@ewowi
ewowi deleted the docs-restructure branch September 12, 2026 20:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants