Skip to content

docs: one canonical URL per page, a product link, robots.txt, sitemap.xml, a 404 page, page descriptions, an honest version label - #380

Closed
luisleo526 wants to merge 2 commits into
mainfrom
sm/en94-cdocs-seo
Closed

luisleo526 wants to merge 2 commits into
mainfrom
sm/en94-cdocs-seo

Conversation

@luisleo526

Copy link
Copy Markdown
Collaborator

cdocs.pineforge.dev is indexed twice per page (/page and /page.html), has no robots.txt or sitemap, carries one meta description on every page, shows a git-describe string as its version (v1.5.0-rc.1-7-g42fa1d4b), and links nowhere to the product site. This fixes what the repository can fix (EN-94, items C1-C5).

What changes

  • C1 docs/_theme/footer.html: every page footer links https://pineforge.dev (both footer branches).
  • C2 new docs/seo_postprocess.py (standard library only; called by docs/build.sh right after Doxygen): adds one rel="canonical" per page (https://cdocs.pineforge.dev/<page>, the landing page as the site root) and rewrites relative page links from X.html to X (index.html to ./, anchors kept). Absolute, mailto:, protocol-relative, root-relative, nested and non-page links, scripts, comments and every non-page file are left alone. Running it twice changes nothing. All pages are validated before any is written; an empty or missing site fails the build.
  • C3 docs/_theme/robots.txt (allow all, names the sitemap) and docs/_theme/404.html are copied to the site root through HTML_EXTRA_FILES (docs/Doxyfile); sitemap.xml is generated from the built pages (sorted, canonical URLs, no 404, no clock data). 404.html is a plain static page (absolute stylesheet links, no scripts) and is left untouched by the post-processing.
  • C4 docs/_theme/header.html: the meta description is built from each page's title; the landing page gets one sentence taken from docs/pages/index.md (the C++17 engine in two layers: a kernel that knows nothing about PineScript or TradingView, and the Pine adapter).
  • C5 docs/build.sh: the version label is the tag name only for a clean checkout of an exact tag, and the word unreleased otherwise.

Please read: C5 (stated)

main is already past the v1.5.0 tag, so the deployed site will read unreleased from the first deploy until the next tag is cut. A build of a tag shows that tag.

Please read: C3 needs a host-side check (not part of this PR; for the site owners)

Today every unknown path on cdocs.pineforge.dev answers 200 with the home page (measured 10-12: /robots.txt, /sitemap.xml, /nonexistent-xyz all 200 text/html). Nothing in this repository produces that; it is host-side configuration of the Cloudflare Pages project pineforge-docs (or whatever fronts the domain), and this PR does not touch Cloudflare. Real files are served correctly (/custom.css 200 text/css), so robots.txt and sitemap.xml will be served as real files after the deploy. For /nonexistent-xyz the decider is
curl -s -o /dev/null -w '%{http_code}\n' https://cdocs.pineforge.dev/nonexistent-xyz:
200 means the host still rewrites unknown paths to /: the change is then to turn off the single-page-app / not-found fallback on the Pages project (in the site owner's Cloudflare account) so the root 404.html is served with status 404. 404 means done.

Notes for the reviewer

  • The sitemap lists every generated top-level page: the docs step built 1143 pages in the lab run, so sitemap.xml has 1142 URLs (well under the 50,000 limit). The ~70 URLs seen in Google's index are a subset.
  • docs/build.sh is a proof-read file, so this PR takes the full CI path (not the documentation-only path). scripts/ci_docs_only.py, scripts/test_ci_docs_only.py and docs/ci.md list docs/seo_postprocess.py next to it. docs/ci.md words changed: "four files a proof job reads" -> "five files" (two places) and the docs/build.sh sentence now names test_docs_seo besides test_docs_doxygen_retry. If this PR is squash-merged, build the squash body from the two commit messages so those namings stay.
  • Why two commits: 0e7a69f7 is the change as reviewed; 9827eaad moves the test_docs_seo registration to the END of tests/CMakeLists.txt, because tests/private_fixture_reads.evidence pins CMake lines by number and a registration in the middle moved a pinned line (the first lab run failed exactly test_private_fixture_reads and test_private_fixture_reads_controls).
  • The self-test (bash docs/build.sh --self-test-seo, CTest row test_docs_seo) runs the real script on a synthetic site and compares the output byte for byte: canonical per page, rewritten and untouched links, escaped descriptions, sitemap URL set, untouched assets, a second pass identical, failure on empty input, the version rule.
  • Order: lands after the corpus gitlink PR corpus: move the gitlink to 9245f28 (probe directories removed, headers state settings only) #376; built on main 2dbd72f; rebased onto Use the shared PineForge favicon in API docs #355 (shared favicon, same HTML_EXTRA_FILES lines) if that lands first.

Verification (lab remote, no tests on the Mac)

  • Job rj-20261012t195837-b0760d (commit 0e7a69f): preflight passed (49 stages, 0 advisory); docs passed (exit 0, Doxygen warnings 0, ==> SEO post-processing: 1143 pages and sitemap.xml). Release, debug, sanitizers, kernel: 2 failures each, test_private_fixture_reads*, fixed by 9827eaa as above.
  • Job rj-20261012t205443-8ec24a (commit 9827eaa, from a worktree of the primary checkout so abi-prior-fetch has its remote): ci_verify release passed, 911 of 911 tests, 38 stages, 0 failures, including test_docs_seo, test_docs_doxygen_retry and test_private_fixture_reads.
  • Job rj-20261012t204300-5f005c (commit 9827eaa): debug exit 0, kernel exit 0; sanitizers: see the PR conversation for the final line.

🤖 Generated with Claude Code

luisleo526 and others added 2 commits October 13, 2026 06:59
….xml, a 404 page, page descriptions, an honest version label

cdocs.pineforge.dev is indexed twice per page (/page and /page.html), has no
robots.txt or sitemap, carries one meta description on every page, shows a
git-describe string as its version, and links nowhere to the product site.
This fixes what the repository can fix (EN-94, items C1-C5):

- C1: every page footer links https://pineforge.dev (both footer branches).
- C2: docs/seo_postprocess.py runs after Doxygen and adds one
  rel=canonical per page (https://cdocs.pineforge.dev/<page>, the landing
  page as the site root) and rewrites relative page links from X.html to X
  (index.html to ./, anchors kept). Absolute, mailto, protocol-relative,
  root-relative, nested and non-page links, scripts, comments and every
  non-page file are left alone; a second pass changes nothing.
- C3: robots.txt (allow all, names the sitemap) and 404.html are copied to the
  site root through HTML_EXTRA_FILES; sitemap.xml is generated from the built
  pages (sorted, canonical URLs, no 404, no clock data). The 404.html is a
  plain static page; whether the host serves it for unknown paths is a host
  setting that this change does not touch.
- C4: the meta description is built from each page's title; the landing page
  gets one sentence from docs/pages/index.md.
- C5: the version label is the tag name only for a clean checkout of an exact
  tag, and the word "unreleased" otherwise, so a git-describe string never
  reaches the site again.

Tests: docs/build.sh --self-test-seo (CTest row test_docs_seo) runs the real
script on a synthetic site and asserts the output byte for byte, the sitemap
URL set, untouched assets, idempotence, failure on empty input, and the
version-label rule. docs/build.sh stays a proof-read file, so
scripts/ci_docs_only.py, scripts/test_ci_docs_only.py and docs/ci.md list
docs/seo_postprocess.py beside it.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
tests/private_fixture_reads.evidence pins CMake lines by number, so adding the
row in the middle of tests/CMakeLists.txt moved a pinned token from line 3251
and failed test_private_fixture_reads and its controls in every profile.
Registered after the last existing line, nothing that was already there moves.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
@luisleo526

Copy link
Copy Markdown
Collaborator Author

Superseded by #390 (part A: Doxyfile and theme) and a part B PR that follows once #390 lands; both rebased onto main 04607c1 (v1.6.0-rc.1) on new branches.

@luisleo526 luisleo526 closed this Oct 14, 2026
luisleo526 added a commit that referenced this pull request Oct 14, 2026
…me (#380, part a) (#390)

Part a of #380 (EN-94), split by ENGINE-TOP and L6 so the theme lands first.
This part is the documentation theme only:

- the page footers link https://pineforge.dev (both footer branches);
- docs/_theme/404.html is copied to the site root through HTML_EXTRA_FILES in
  docs/Doxyfile;
- the page template's meta description is built from each page's title instead
  of one sentence on every page.

Part b (docs/build.sh, docs/seo_postprocess.py, scripts/ci_docs_only.py,
scripts/test_ci_docs_only.py, tests/CMakeLists.txt, and with them
docs/_theme/robots.txt, the robots.txt line of docs/Doxyfile and docs/ci.md)
adds the canonical URLs, the sitemap, robots.txt, the version label and the
self-test. robots.txt moves with it because it names the sitemap that only
part b generates.

Co-authored-by: Claude Sonnet 5.5 <[email protected]>
luisleo526 added a commit that referenced this pull request Oct 15, 2026
… version label (#380, part b) (#394)

* docs: one canonical URL per page, robots.txt, a sitemap and an honest version label, with their self-test (#380, part b)

Part b of #380 (EN-94), split by ENGINE-TOP and L6; part a (the theme: the 404
page, per-page descriptions and the product link) lands first.

cdocs.pineforge.dev is indexed twice per page (/page and /page.html), has no
robots.txt or sitemap, and shows a git-describe string as its version. This part
is the build, its output and its test:

- docs/seo_postprocess.py runs after Doxygen and adds one rel=canonical per
  page (https://cdocs.pineforge.dev/<page>, the landing page as the site root)
  and rewrites relative page links from X.html to X (index.html to ./, anchors
  kept). Absolute, mailto, protocol-relative, root-relative, nested and non-page
  links, scripts, comments and every non-page file are left alone; a second pass
  changes nothing. It sets one meta description per page, built from the page
  title (the landing page gets one sentence condensed from docs/pages/index.md),
  and generates sitemap.xml from the built pages (sorted, canonical URLs, no 404,
  no clock data).
- docs/_theme/robots.txt (allow all, names the sitemap) is copied to the site
  root through HTML_EXTRA_FILES in docs/Doxyfile; it lands here because the
  sitemap it names is generated by this part.
- docs/build.sh calls the post-process after Doxygen and sets the version label
  to the tag name only for a clean checkout of an exact tag and to the word
  "unreleased" otherwise, so a git-describe string never reaches the site again.
  `docs/build.sh --self-test-seo` runs the real script on a synthetic site and
  asserts the output byte for byte, the sitemap URL set, untouched assets,
  idempotence, failure on empty input, and the version-label rule.
- tests/CMakeLists.txt registers it as the CTest row test_docs_seo at the end of
  the file, so no earlier line moves (the private fixture read evidence pins CMake
  lines by number).
- docs/build.sh stays a proof-read file and docs/seo_postprocess.py joins it, so
  scripts/ci_docs_only.py (PROOF_READS) and scripts/test_ci_docs_only.py list
  both, and docs/ci.md says so: five files a proof job reads.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

* docs: run the SEO self-test in the docs build job instead of CTest

The self-test of docs/seo_postprocess.py, `bash docs/build.sh
--self-test-seo`, ran as the CTest row test_docs_seo. A CMake row makes a
documentation change look like an engine change. The docs workflow's build
job now runs the same command, unchanged, before the real site build, on
every pull request, push to main or a v* tag, and manual dispatch. It needs
no CMake configure and no C++ build, and a failing self-test stops the job.

The CMake row and its comment are removed from tests/CMakeLists.txt, so no
earlier line of that file moves. The sentences that named the CTest row
(docs/build.sh header, docs/ci.md, and the two PROOF_READS reasons in
scripts/ci_docs_only.py) now name the workflow step. Neither the
self-test body nor docs/seo_postprocess.py changes.

This replaces, on purpose, sentences that dc77b26 added to docs/ci.md and
scripts/ci_docs_only.py: the ones saying `docs/build.sh`, whose self-tests
are `test_docs_doxygen_retry` and `test_docs_seo`; and
`docs/seo_postprocess.py`, which `test_docs_seo` executes on synthetic pages.
It also rewrites the older docs/ci.md sentence from 133f571, "`docs.yml`
builds the Doxygen site on every pull request and push, as before", which
now also says that the build job runs the self-test first.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

---------

Co-authored-by: Claude Sonnet 5.5 <[email protected]>
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.

1 participant