From c1a11460bc850e20312076f6ffe4bbd25d82b40b Mon Sep 17 00:00:00 2001 From: trios-phd-agent Date: Sat, 23 May 2026 10:13:31 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=90=20feat(trios-phd)=20TRIOS=5FPHD=5F?= =?UTF-8?q?NO=5FIMAGE=5FTRAIN:=20anchor=20hero=20panels=20via=20Needspace,?= =?UTF-8?q?=20ban=20hard=20clearpage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the renderer-layer image-placement rule for the PhD monograph PDF pipeline. Hero panels must travel with the nearest substantive heading and its body text — never as a gallery / image train, never with a heading orphaned above its figure. Implementation (renderer / template / filter layer only — no per-chapter edits, no PDF post-processing): - docs/phd/main.tex: \usepackage{needspace} + soft keep-together wrappers for \section and \chapter via \Needspace*{0.58\textheight}. - templates/chapter.template.tex: same wrapper for the per-chapter pandoc build, plus \Needspace* inside \chapterhero so the chapter heading and hero figure cannot be split. - filters/force-fullwidth-hero.lua: documentation clarifies the one-hero-per-chapter promotion and its role in the no-image-train rule. \Needspace* (starred form) only triggers a page break when remaining vertical space is insufficient AND we are not already at the top of a page — so it never produces gratuitous breaks. A previous hard \clearpage approach produced short title-only pages and was rejected by manual QA; the new doc explicitly forbids it. Docs: - docs/pdf-rendering.md (NEW): canonical contract for the PhD PDF pipeline, including TRIOS_PHD_CANONICAL_PIPELINE, TRIOS_PHD_RENDERER_FIRST, TRIOS_PHD_STYLE_LOCK, TRIOS_PHD_NO_IMAGE_TRAIN, why hard \clearpage is forbidden, and the QA commands. - AGENTS.md, docs/phd/README.md: pointer sections linking to docs/pdf-rendering.md. Local checks: - cargo check -p trios-phd: OK. - tectonic smoke test on a minimal document exercising the \section wrapper + \Needspace*: PDF builds cleanly. - Full book / chapter PDF generation requires Railway SSOT credentials and is intentionally not run here; the existing trios-phd compile-chapters / build-book paths are unchanged and will pick up the new template rules automatically. Closes #957 Agent: PHD-RENDERER Co-Authored-By: Claude Opus 4.7 --- AGENTS.md | 34 +++++ docs/pdf-rendering.md | 231 +++++++++++++++++++++++++++++++ docs/phd/README.md | 14 ++ docs/phd/main.tex | 40 ++++++ filters/force-fullwidth-hero.lua | 8 ++ templates/chapter.template.tex | 19 +++ 6 files changed, 346 insertions(+) create mode 100644 docs/pdf-rendering.md diff --git a/AGENTS.md b/AGENTS.md index 46fb7d2307..99eb20035c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -158,3 +158,37 @@ cargo clippy --all-targets -- -D warnings || exit 1 3. Если да — **ОСТАНОВИСЬ** и открой обсуждение в issue. Если пропустил этот шаг — PR будет закрыт автоматически. + +--- + +## TRIOS_PHD — PDF rendering rules + +The TRIOS PhD monograph PDF pipeline has its own canonical contract. +Every agent touching the renderer, templates, hero images, or chapter +sources MUST read and follow: + +- **[docs/pdf-rendering.md](docs/pdf-rendering.md)** — canonical pipeline. + +Headline rules (full detail in the doc above): + +- `TRIOS_PHD_CANONICAL_PIPELINE` — Rust TRIOS MCP / `trios-phd` → + Railway/Postgres SSOT → Markdown → pandoc → LaTeX → tectonic → PDF. + PDF is an export target, never a source of truth. +- `TRIOS_PHD_RENDERER_FIRST` — placement / typography / image changes + go in `templates/chapter.template.tex`, `docs/phd/main.tex`, or + `filters/force-fullwidth-hero.lua`. Never patch individual chapters + for visual concerns and never post-process the PDF. +- `TRIOS_PHD_STYLE_LOCK` — white academic title page, serif typography, + large engraved / ornamental S3AI hero panels, book margins, large + images. QA baseline: 150 A4 pages, qpdf clean, 0 duplicate long + paragraphs, 0 duplicate numbered headings, 0 Cyrillic hits, 0 + secret / stale / math anomalies, 0 very-short non-empty pages, + at most 1 image-heavy / low-context page (the title page). +- `TRIOS_PHD_NO_IMAGE_TRAIN` — hero panels are anchored to the + nearest substantive heading and body, never grouped as a gallery / + image train. Enforced by `\Needspace*{0.58\textheight}` before every + `\section` and `\chapter` (soft keep-together), defined in + `docs/phd/main.tex` and `templates/chapter.template.tex`. +- Hard `\clearpage` before sections / hero figures is **forbidden**: + it produced short title-only pages and was rejected by QA. Tune the + `\Needspace*` reservation size instead. diff --git a/docs/pdf-rendering.md b/docs/pdf-rendering.md new file mode 100644 index 0000000000..a12a59fca5 --- /dev/null +++ b/docs/pdf-rendering.md @@ -0,0 +1,231 @@ +# TRIOS PhD PDF Rendering — Canonical Rules + +This document is the source of truth for how the TRIOS PhD monograph is +rendered to PDF. Every agent and developer touching the renderer, the +templates, or the chapter sources MUST follow these rules. + +It is repo-canonical and overrides ad-hoc renderer scripts. + +--- + +## TRIOS_PHD_CANONICAL_PIPELINE + +The only supported PDF pipeline is: + +``` +Rust TRIOS MCP / trios-phd + │ + ▼ +Railway / Postgres (ssot.chapters) ← single source of truth (SSOT) + │ + ▼ +Markdown (body_md + leading hero image) + │ + ▼ pandoc + Lua filter (filters/force-fullwidth-hero.lua) + │ + template (templates/chapter.template.tex + │ or docs/phd/main.tex for the full book) + ▼ +LaTeX (.tex) + │ + ▼ tectonic + ▼ +PDF (print/export target — NOT a source of truth) +``` + +Reference entry points: + +- Per-chapter compile: `crates/trios-phd` → `trios-phd compile-chapters` + (`crates/trios-phd/src/main.rs`, around the `CompileChapters` subcommand). +- Full book build: `docs/phd/main.tex` (the `book` class scaffold), + compiled by `tectonic` via `trios-phd build-book` / + `trios-phd compile-resilient`. +- Per-chapter pandoc template: `templates/chapter.template.tex`. +- Hero-image Lua filter: `filters/force-fullwidth-hero.lua`. + +**Do not invent a parallel ReportLab / Python PDF generator.** PDF is an +export from the renderer; it is not a hand-authored artefact. + +--- + +## TRIOS_PHD_RENDERER_FIRST + +If a placement, typography, or image rule must change, change it in the +renderer (template, Lua filter, or `docs/phd/main.tex`), NOT by editing +the per-chapter `.tex` files or by post-processing PDFs. + +Order of preference for any structural change: + +1. `templates/chapter.template.tex` (per-chapter pandoc template). +2. `docs/phd/main.tex` (book preamble for the full monograph). +3. `filters/force-fullwidth-hero.lua` (Markdown → AST transform). +4. As a last resort: a Rust function in `crates/trios-phd`. + +Editing individual chapters is allowed only for content. Visual / +placement / image-train concerns belong to the renderer layer. + +--- + +## TRIOS_PHD_STYLE_LOCK + +The accepted PhD visual style is locked: + +- White academic title page. +- Serif typography (`DejaVu Serif` for main, math via classic CM). +- Large black-and-white engraved / ornamental TRIOS S3AI hero panels. +- Book-style margins (`docs/phd/main.tex` geometry block). +- Large, centered hero images — never thumbnails. + +QA baseline (manual build that defines "accepted"): + +- 150 A4 pages. +- `qpdf --check`: clean. +- Exact duplicate long paragraphs: 0. +- Duplicate numbered headings: 0. +- Cyrillic hits in the body: 0 (English-only repo docs). +- Secret / stale / math anomaly hits: 0. +- Very short non-empty pages: 0. +- Image-heavy / low-context candidate pages: at most 1 (the title page). + +Any renderer change that regresses this baseline is a failure. + +--- + +## TRIOS_PHD_NO_IMAGE_TRAIN + +This is the non-negotiable rule that motivates the current renderer +configuration. + +### Statement + +Hero panels MUST NOT be laid out as a gallery or image train. Each hero +panel must be semantically anchored to the nearest substantive heading +and its body text. A heading must never be orphaned at the bottom of a +page above its hero figure, and hero figures from neighbouring sections +must never collapse into a multi-image train on a single page. + +### How it is enforced + +A **soft keep-together** vertical reservation, sized to fit a heading +plus a full hero figure (~`0.58\textheight`), is inserted before every +`\section` and `\chapter` via the `needspace` package: + +```latex +\usepackage{needspace} + +\makeatletter +\let\trios@orig@section\section +\renewcommand{\section}{% + \Needspace*{0.58\textheight}% + \trios@orig@section +} +\let\trios@orig@chapter\chapter +\renewcommand{\chapter}{% + \Needspace*{0.58\textheight}% + \trios@orig@chapter +} +\makeatother +``` + +This block lives in: + +- `docs/phd/main.tex` — for the full monograph build. +- `templates/chapter.template.tex` — for the per-chapter pandoc build. +- `\chapterhero{}{}` in `templates/chapter.template.tex` is preceded by + `\Needspace*{0.58\textheight}` as well, so the chapter heading + hero + cannot be split. + +`\Needspace*` (starred form) is critical: it triggers a page break only +when the remaining vertical space is insufficient AND we are not already +at the top of a page. It does not insert gratuitous breaks. + +### Why hard `\clearpage` is FORBIDDEN + +`\clearpage` before every section was tried and rejected. It produced +short title-only pages (a heading at the top of a fresh page, then a +hero figure on the next page) and broke the QA baseline. **Never insert +`\clearpage` before sections or hero figures as a workaround.** If a +heading still appears orphaned, raise the reservation size (e.g. to +`0.62\textheight`) — do not switch to `\clearpage`. + +### What about the Lua filter? + +`filters/force-fullwidth-hero.lua` promotes exactly one hero image per +chapter to full text width at block position 1. Additional standalone +images stay where the author placed them, anchored to their nearest +heading. The filter is part of the no-image-train discipline, not in +conflict with it. + +### Image size + +Hero images remain large, centered, full text width, and PhD-style. The +no-image-train rule is about placement, not about shrinking or removing +images. + +--- + +## QA commands + +Run these after any renderer-touching change: + +```bash +# Build the per-chapter PDFs (no secrets needed beyond local pandoc + tectonic). +cargo run -p trios-phd -- compile-chapters \ + --chapters-dir docs/golden-sunflowers \ + --template templates/chapter.template.tex \ + --lua-filter filters/force-fullwidth-hero.lua \ + --out-dir docs/golden-sunflowers/pdf + +# Build the full monograph (requires the full chapter source tree under docs/phd/). +cargo run -p trios-phd -- build-book + +# Validate the produced PDF. +qpdf --check build/phd.pdf +pdfinfo build/phd.pdf +pdftotext -layout build/phd.pdf build/phd.txt + +# QA scans on the extracted text. +grep -nE '[А-Яа-яЁё]' build/phd.txt || echo "OK: no Cyrillic" +grep -nE 'TODO|FIXME|XXX|STALE' build/phd.txt || echo "OK: no stale markers" +grep -nE '(password|secret|token|api[_-]?key)=' build/phd.txt \ + || echo "OK: no secrets" + +# Duplicate-heading and duplicate-paragraph scans live in trios-phd's +# audit subcommand and the tools under tools/page_gate/ — run them and +# compare to the baseline: +cargo run -p trios-phd -- audit +``` + +Visually inspect: + +- The title page (white academic, large engraved S3AI panel). +- The first body pages of each Part — confirm no section heading sits + alone at the bottom of a page above its hero figure. +- Any chapter that previously triggered an image train — confirm only + one hero per chapter at the top, and any in-body images sit with + their semantic owners. + +--- + +## What NOT to do + +- Do NOT add `\clearpage` before sections or hero figures. +- Do NOT shrink hero images to "fix" pagination — adjust + `\Needspace*{...}` instead. +- Do NOT post-process the rendered PDF to move images. +- Do NOT introduce a parallel Python / ReportLab generator. +- Do NOT edit individual chapter `.tex` files for placement concerns. +- Do NOT print, log, or commit Railway tokens / database passwords + while running the pipeline. + +--- + +## Pointers + +- `crates/trios-phd/src/main.rs` — Rust orchestration of the pipeline. +- `docs/phd/main.tex` — book preamble, includes `needspace` and the + `\section` / `\chapter` keep-together wrapper. +- `templates/chapter.template.tex` — per-chapter pandoc template, + includes the same wrapper and `\chapterhero`. +- `filters/force-fullwidth-hero.lua` — one-hero-per-chapter promotion. +- `assets/illustrations/` — hero panel sources. +- `AGENTS.md` — short pointer to this document under TRIOS_PHD. diff --git a/docs/phd/README.md b/docs/phd/README.md index 67e367f4d1..7f4d60df82 100644 --- a/docs/phd/README.md +++ b/docs/phd/README.md @@ -72,6 +72,20 @@ make pdf # → out/main.pdf (~969 pages, 182 figures, ~24 MB after gs /ebook compression) ``` +### Renderer rules (image placement, keep-together, image-train ban) + +The canonical rules for image placement, hero-panel anchoring, and +typography are defined in: + +- **[../pdf-rendering.md](../pdf-rendering.md)** — `TRIOS_PHD_CANONICAL_PIPELINE`, + `TRIOS_PHD_RENDERER_FIRST`, `TRIOS_PHD_STYLE_LOCK`, + `TRIOS_PHD_NO_IMAGE_TRAIN`. + +Headline: hero panels are anchored to the nearest substantive heading +via `\Needspace*{0.58\textheight}` (soft keep-together). Hard +`\clearpage` before sections is forbidden — it produced short +title-only pages and was rejected by QA. + --- ## 📜 Audit trail diff --git a/docs/phd/main.tex b/docs/phd/main.tex index b4502e4f58..34c807585a 100644 --- a/docs/phd/main.tex +++ b/docs/phd/main.tex @@ -30,6 +30,13 @@ % Float placement helpers (R1/L1) \usepackage{float} +% TRIOS_PHD_NO_IMAGE_TRAIN — soft keep-together for a section heading and the +% hero/context block that follows. We reserve ~0.58\textheight before every +% \section, so a heading is never orphaned at the bottom of a page above a +% hero image. This is the canonical mechanism: a SOFT vertical reservation, +% NOT a hard \clearpage. A hard \clearpage before every section created +% short title-only pages and was rejected by QA. +\usepackage{needspace} \usepackage{algorithm} \usepackage{algpseudocode} \usepackage{epigraph} @@ -293,6 +300,39 @@ % Define `invariant` environment as a styled tcolorbox \newtheorem{invariant}{Invariant} +% ===================================================================== +% TRIOS_PHD_NO_IMAGE_TRAIN — semantic image anchoring. +% +% Rule: a hero panel must travel with the nearest substantive heading +% and its body text, not float into an image train and not strand a +% heading alone above a figure. We achieve this with a SOFT keep- +% together reservation in front of every \section and \chapter, sized +% so the heading plus a hero figure (~0.58 \textheight) cannot be split +% across a page boundary. +% +% IMPORTANT: this is a SOFT reservation, not \clearpage. A hard +% \clearpage before every section produces short title-only pages and +% was rejected by manual QA. Do not replace this with \clearpage. +% +% Implementation: wrap \section and \chapter so the original command is +% preceded by \Needspace*{0.58\textheight}. \Needspace* (starred form) +% triggers a page break only when the remaining vertical space is +% insufficient AND we are not already at the top of a page — so it +% never inserts a gratuitous break. +% ===================================================================== +\makeatletter +\let\trios@orig@section\section +\renewcommand{\section}{% + \Needspace*{0.58\textheight}% + \trios@orig@section +} +\let\trios@orig@chapter\chapter +\renewcommand{\chapter}{% + \Needspace*{0.58\textheight}% + \trios@orig@chapter +} +\makeatother + \begin{document} % Front matter diff --git a/filters/force-fullwidth-hero.lua b/filters/force-fullwidth-hero.lua index 97eacc5f13..ba3f6574cb 100644 --- a/filters/force-fullwidth-hero.lua +++ b/filters/force-fullwidth-hero.lua @@ -12,6 +12,14 @@ --- `hero-image` and `hero-caption`, so chapter.template.tex can render --- it via \chapterhero{}{} at the top. --- +--- TRIOS_PHD_NO_IMAGE_TRAIN: this filter promotes EXACTLY ONE hero image +--- per chapter. Additional standalone images are left in place at their +--- authored positions so they stay semantically anchored to the nearest +--- substantive heading and body text, never grouped as a gallery / image +--- train. The keep-together discipline (\Needspace before \section and +--- \chapter) is enforced in templates/chapter.template.tex and +--- docs/phd/main.tex — NOT here, and NOT via \clearpage. +--- --- Pairs with: --- - templates/chapter.template.tex --- - migrations/005_hero_fullwidth.sql (which prepends a Markdown image diff --git a/templates/chapter.template.tex b/templates/chapter.template.tex index 297f0b6962..16bd3e3a98 100644 --- a/templates/chapter.template.tex +++ b/templates/chapter.template.tex @@ -17,11 +17,19 @@ \usepackage{hyperref} \usepackage{caption} \usepackage{fontspec} % requires xelatex / lualatex / tectonic +% TRIOS_PHD_NO_IMAGE_TRAIN — see docs/pdf-rendering.md. +% Soft keep-together so a section heading is never orphaned above its +% hero image. This is NOT a hard \clearpage (which produces title-only +% pages and was rejected by QA). +\usepackage{needspace} % Hero figure: ALWAYS full text-width, ALWAYS at the start of the chapter. % #1 = path or URL of the image (resolved before pandoc invocation) % #2 = optional caption text (rendered in italic, no figure number) \newcommand{\chapterhero}[2]{% + % TRIOS_PHD_NO_IMAGE_TRAIN: reserve enough vertical space so the chapter + % heading + hero figure cannot be split. Soft, not \clearpage. + \Needspace*{0.58\textheight}% \begin{figure}[H] \centering \includegraphics[width=\linewidth,keepaspectratio]{#1}% @@ -30,6 +38,17 @@ \vspace{0.5em}% } +% TRIOS_PHD_NO_IMAGE_TRAIN: wrap \section so headings stay with the body +% (and any hero/context block immediately following). SOFT keep-together +% only — never a hard \clearpage. +\makeatletter +\let\trios@orig@section\section +\renewcommand{\section}{% + \Needspace*{0.58\textheight}% + \trios@orig@section +} +\makeatother + \begin{document} % First block of every chapter is the hero image (injected by the Lua filter