feat(cms): U2 render content/ into detail pages (characterization-verified)

Add tools/build_content.py: renders every content/ entry into its blog /
news / event detail page by self-templating the page's own chrome and
replacing only content-derived regions (title, article header, hero,
body, and — for new pages only — the scrollspy).

Faithful reproduction, proven against the live site:
- body_format: html fragments injected verbatim, so guide-boxes, inline
  figures and event galleries render unchanged.
- scrollspy sidebars are preserved (labels are hand-curated, shorter than
  the H2s); only generated when a page has none.
- <title> suffix derived per page, so EN/SI " - MSOS", MK " - МСОС", and
  suffix-less event titles all reproduce exactly.
- hero /images/… rewritten to the page's ../../../ depth.
- ASCII-only console output (no cp1252 crash on Windows/CI).

tools/tests/test_build_content.py: 8 characterization tests; the core one
renders all 51 pages and asserts each is semantically equivalent
(whitespace/attr-order-insensitive) to the current page — the R5
no-regression guarantee.

Does NOT overwrite the committed pages; whether generated pages are
committed vs built fresh in CI is decided in U4/U5. Run:
  python tools/build_content.py --check   # verify equivalence
  python tools/build_content.py --write    # emit pages (for U4)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
popovskikandClaude Opus 4.8 committed 2026-08-08 16:36:11 +02:00
1 parent 9ddd0f88ef
commit 596290acf8
3 files changed
+379

No files matched your search

+12
View File
@@ -91,6 +91,18 @@ The scaffold is a starting point a human proofs, **not** a guaranteed lossless
transform — pages the extractor cannot parse cleanly are reported, not
silently emitted.
## Deferred refinements
- **`toc` (blog/event scrollspy labels).** The sidebar labels are often
hand-shortened relative to the `<h2>` headings (e.g. heading "How an idea for
socializing became the start of a movement" → sidebar "Idea to Movement"). To
avoid regressing that editorial curation, U2 **preserves an existing populated
scrollspy** and only auto-generates one for a page that has none. A future
optional `toc:` front-matter list (ordered `{id, label}` pairs) would let the
CMS edit those short labels directly. Not needed to reproduce today's pages.
- **`mobile_title`.** Some blog pages use a shortened mobile header title; U2
preserves the existing one and does not overwrite it.
## Non-goals for U1
- No CMS wiring (U7), no build/render of pages (U2), no listings/date refactor