diff --git a/.gitea/workflows/slides.yml b/.gitea/workflows/slides.yml index 31c7ebb..0a72c58 100644 --- a/.gitea/workflows/slides.yml +++ b/.gitea/workflows/slides.yml @@ -1,11 +1,15 @@ # Nova Slides Render — re-renders presentation deck when source files change. +# REQ-273: install python-pptx, pin CLI versions, stage HTML + both PPTX + +# base64-inlined images. name: Nova Slides Render on: push: paths: - 'docs/presentations/**' - 'scripts/render_slides.sh' - - 'assets/nova-sp-theme.css' + - 'scripts/inline_images.py' + - 'scripts/render_pptx.py' + - 'pyproject.toml' workflow_dispatch: jobs: @@ -16,16 +20,24 @@ jobs: with: { fetch-depth: 0 } - uses: actions/setup-node@v4 with: { node-version: '20' } - - name: Install Chrome + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Install python-pptx (slides extra) + run: pip install -e ".[slides]" + - name: Install + pin render CLIs run: | - npx --yes @marp-team/marp-cli@latest --version - npx --yes @mermaid-js/mermaid-cli --version + npx --yes @marp-team/marp-cli@4.5.0 --version + npx --yes @mermaid-js/mermaid-cli@11.16.0 --version - name: Render slides run: bash scripts/render_slides.sh - name: Commit rendered artifacts run: | git config user.name "nova-slides-bot" git config user.email "bot@nova.local" - git add docs/presentations/*.html docs/presentations/*.pptx docs/presentations/assets/png/*.png + git add docs/presentations/*.html \ + docs/presentations/*.pptx \ + docs/presentations/*-python.pptx \ + docs/presentations/assets/png/*.png git diff --cached --quiet || git commit -m "chore(slides): re-render deck [skip ci]" - git push + git push \ No newline at end of file diff --git a/.github/workflows/slides.yml b/.github/workflows/slides.yml index 31c7ebb..0a72c58 100644 --- a/.github/workflows/slides.yml +++ b/.github/workflows/slides.yml @@ -1,11 +1,15 @@ # Nova Slides Render — re-renders presentation deck when source files change. +# REQ-273: install python-pptx, pin CLI versions, stage HTML + both PPTX + +# base64-inlined images. name: Nova Slides Render on: push: paths: - 'docs/presentations/**' - 'scripts/render_slides.sh' - - 'assets/nova-sp-theme.css' + - 'scripts/inline_images.py' + - 'scripts/render_pptx.py' + - 'pyproject.toml' workflow_dispatch: jobs: @@ -16,16 +20,24 @@ jobs: with: { fetch-depth: 0 } - uses: actions/setup-node@v4 with: { node-version: '20' } - - name: Install Chrome + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Install python-pptx (slides extra) + run: pip install -e ".[slides]" + - name: Install + pin render CLIs run: | - npx --yes @marp-team/marp-cli@latest --version - npx --yes @mermaid-js/mermaid-cli --version + npx --yes @marp-team/marp-cli@4.5.0 --version + npx --yes @mermaid-js/mermaid-cli@11.16.0 --version - name: Render slides run: bash scripts/render_slides.sh - name: Commit rendered artifacts run: | git config user.name "nova-slides-bot" git config user.email "bot@nova.local" - git add docs/presentations/*.html docs/presentations/*.pptx docs/presentations/assets/png/*.png + git add docs/presentations/*.html \ + docs/presentations/*.pptx \ + docs/presentations/*-python.pptx \ + docs/presentations/assets/png/*.png git diff --cached --quiet || git commit -m "chore(slides): re-render deck [skip ci]" - git push + git push \ No newline at end of file diff --git a/docs/presentations/README.md b/docs/presentations/README.md index e04a0cd..d9f3593 100644 --- a/docs/presentations/README.md +++ b/docs/presentations/README.md @@ -2,147 +2,184 @@ Leadership-facing presentation decks for the Nova platform. -## The 4-step slide creation process +## The 3-step slide creation process -Every presentation in this folder is produced by the same four-step process. -**Never edit the Marp deck, the PPTX, or the talking points directly** — -always start from the full markdown source of truth (Step 1), synthesize the -Marp deck (Step 2), export to HTML + PPTX (Step 3), then distill the talking -points (Step 4). This keeps a reviewable, plain-text source of truth for -every deck and a presenter-ready cue sheet for delivery. +Every presentation in this folder is produced by the same three-step +process. **Never edit the rendered HTML, either PPTX, or the talking +points directly** — always start from the Marp deck source of truth +(Step 1), render it (Step 2), then distill the talking points (Step 3). +This keeps a reviewable, plain-text source of truth for every deck and a +presenter-ready cue sheet for delivery. ``` -Step 1: full markdown Step 2: Marp deck Step 3: HTML + PPTX Step 4: Talking points -(source of truth) ──► (lean, 21 slides) ──► (rendered) ──► (presenter cues) -*.md *-marp.md *.html / *.pptx *-talking-points.md -+ speaker notes + embedded PNG diagrams + 3-6 bullets per slide -+ mermaid code blocks + Marp frontmatter + key takeaway per slide - + no speaker notes + indexed by Marp slide # - + no maturity badges + content distilled from Step 1 - + no version in footer +Step 1: Author the deck Step 2: Render Step 3: Talking points +(source of truth) ──► (HTML + dual PPTX) ──► (presenter cues) +*-marp.md *.html *-talking-points.md ++ ## Slide N — Title + mermaid PNGs + 3-6 bullets per slide ++ + MARP PPTX (image-of-slide) + key takeaway per slide ++ + python PPTX (structured) + indexed by slide # ++
+ base64-inlined HTML + content distilled from ++ embedded PNG diagrams (self-contained) the Marp deck ``` -### Step 1 — Full markdown (source of truth) +### Step 1 — Author the deck (source of truth) -**File convention:** `.md` (e.g. `nova-autonomous-cloud-delivery.md`). +**File convention:** `-marp.md` (e.g. +`nova-autonomous-cloud-delivery-marp.md`). -Write the complete deck as a standard markdown file. This is the **source of -truth** — it contains: +This is the **sole source of truth** — the Marp deck that is both authored +and rendered. It contains: -- Every slide as an `## Slide N — Title` H2 section. +- **Marp frontmatter** at the top: `marp: true`, `theme: default`, + `paginate: true`, `size: 16x9`, a header/footer, and an inline `style:` + block carrying the S&P palette (`#D6002A` red, `#1B1B1B` black, the + `section.title` rule). The styling is **inline** — no standalone theme + CSS is loaded at render time. +- Every slide as an `## Slide N — Title` (or `## Appendix A1 — Title`) H2 + section. The H1 title slide precedes slide 1. - Tight bullets with leadership-relevant content. -- A `> **Speaker notes:**` block at the end of each slide with the nuance, - the "who cares and why," and the honesty caveats. -- Mermaid diagrams as ```` ```mermaid ```` fenced code blocks (these render - on GitHub/Pages but not in Marp — Step 2 converts them to images). -- An honest "shipped vs. deferred" framing: every "available today" claim is - grounded in shipped/verified work; every "deferred" item is explicitly +- **Speaker notes** as `` HTML comments at the + end of each slide. Marp excludes HTML comments from the rendered slide; + they are for authors/presenters only. +- **Talking points** as `` HTML comments (also + excluded from rendering — Step 3 mirrors them into a standalone cue + sheet). +- **Benefit callouts** as `
...
` (styled by the + inline `style:` block — italic, S&P-red top border). No `**Benefit:**` + text prefixes. +- Mermaid diagrams **pre-rendered to PNG** under `assets/png/` and embedded + with `![w:1000](assets/png/.png)` (or `h:480 class:tall` for tall + images). The `.mmd` sources live under `assets/mmd/`. +- **No maturity badges**, **no version in the footer**, **no internal + decision/requirement IDs or `.py` file paths** in the slide bodies + (those live in the `.ciagent/` files only; speaker-note HTML comments are + exempt). +- An honest "shipped vs. deferred" framing: every "available today" claim + is grounded in shipped/verified work; every "deferred" item is explicitly marked with the blocking work in plain language. -**Why this file is the source of truth:** it is reviewable in any markdown -viewer, diffs cleanly in git, and carries the full reasoning (speaker notes) -that a presenter needs. The Marp deck and PPTX are *derived artifacts* — if a -fact is wrong, fix it here and re-run Steps 2 and 3. +**Why the Marp deck is the source of truth:** it is reviewable in any +markdown viewer, diffs cleanly in git, and carries the full reasoning +(speaker notes) that a presenter needs. The HTML and PPTX are *derived +artifacts* — if a fact is wrong, fix it here and re-run Step 2. -### Step 2 — Marp deck synthesis +> **`nova-sp-theme.css` is RETIRED from render.** The standalone theme +> stylesheet under `assets/nova-sp-theme.css` is kept as a **reference +> only** and is **not loaded at render time**. The live styling is the +> inline `style:` block in the `-marp.md` frontmatter. Do NOT pass the CSS +> via `--theme`; it is not in the render path. -**File convention:** `-marp.md` (e.g. `nova-autonomous-cloud-delivery-marp.md`). +### Step 2 — Render (HTML + dual PPTX) -Synthesize the full markdown into a lean Marp deck: +`bash scripts/render_slides.sh [deck-name]` renders the Marp deck +end-to-end: -- **Marp frontmatter** at the top: `marp: true`, `theme: nova-sp`, - `paginate: true`, `size: 16x9`, a header/footer, and an inline `style:` - block for fonts, colors, tables. -- **No speaker notes.** The Marp deck is what the audience sees; the - speaker notes live only in the Step 1 source of truth. -- **Mermaid diagrams → PNG images.** Marp does not render mermaid fenced - blocks natively. Extract each mermaid block from Step 1 into a `.mmd` - source file under `assets/mmd/`, render it to PNG under `assets/png/`, - and embed it with `![w:1000](assets/png/.png)`. -- **`` + ``** on title and - closing slides for the dark-background title style. -- **No maturity badges.** The deck no longer uses `` - spans. Deferred items are named in plain language with their blocking - work, not tagged with a badge. -- **No version in the footer.** The footer carries the deck title only. -- **Tighter prose** than Step 1 — strip the speaker-note nuance; keep the - leadership-relevant selling points. - -### Step 3 — Render to HTML and PPTX - -Both formats are derived from the Marp deck. **HTML is committed to the repo** -(viewable in any browser, self-contained with base64-embedded images). **PPTX -is also committed to the repo** as a first-class binary artifact and is -attached to the phase's release via `scripts/attach_release_asset.py`. +1. **Mermaid PNGs** — each `assets/mmd/*.mmd` → `assets/png/*.png` + (S&P-themed via `sp-theme.json`, 2x scale, transparent background). +2. **MARP HTML** — `*-marp.md` → `*.html` (S&P inline style, Marp default + theme). Pinned `@marp-team/marp-cli@4.5.0`. +3. **MARP PPTX** — `*-marp.md` → `*.pptx` (image-of-slide PPTX; the primary + release attachment). +4. **Inline images** — `scripts/inline_images.py` rewrites the HTML to + base64-embed every `assets/` image so the HTML is self-contained (no + external asset folder needed for redistribution). +5. **python PPTX** — `scripts/render_pptx.py` produces a second, + structured, editable PPTX (`*-python.pptx`) with native text boxes, + native tables, embedded pictures, and italic benefit callouts. +6. **Stage** — all rendered artifacts (PNGs + HTML + both PPTX) are + `git add`-ed for commit. ```bash -CHROME_PATH=/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome \ - npx --yes @marp-team/marp-cli@latest --allow-local-files \ - docs/presentations/-marp.md \ - -o docs/presentations/.html +bash scripts/render_slides.sh nova-autonomous-cloud-delivery ``` -HTML export inlines images as base64 data URIs. PPTX export requires -`--allow-local-files` so the local PNG diagrams are embedded in the file. -The render + commit + attach pipeline is automated by `scripts/render_slides.sh`. +Both the HTML and both PPTX files are committed to the repo; the MARP +PPTX is also attached to the phase's release via +`scripts/attach_release_asset.py`. -### Step 4 — Talking points (presenter cues) +#### Dual-PPTX output + +| PPTX | File | Render | Purpose | +|---|---|---|---| +| **MARP PPTX** | `*.pptx` | `@marp-team/marp-cli` (Chrome screenshot of each slide) | Image-of-slide; the primary release attachment (pixel-perfect, not editable) | +| **python PPTX** | `*-python.pptx` | `scripts/render_pptx.py` (python-pptx) | Structured, editable PPTX (native text boxes, tables, pictures) for comparison/editing | + +### Step 3 — Talking points (presenter cues) **File convention:** `-talking-points.md` (e.g. `nova-autonomous-cloud-delivery-talking-points.md`). -Distill the source of truth (Step 1) into presenter-ready cues, indexed by -the Marp deck (Step 2) slide structure: +Distill the deck's `` HTML comments into +presenter-ready cues, indexed by the Marp deck (Step 1) slide structure: - **One section per Marp slide** — `## Slide N — Title`, matching the Marp deck's 20 main + 1 appendix slide structure exactly. -- **3-6 talking point bullets per slide** — punchy, actionable cues distilled - from the source markdown's speaker notes. +- **3-6 talking point bullets per slide** — punchy, actionable cues + distilled from the Marp deck's `` comments. - **Key takeaway per slide** — the one memorable thing the audience should walk away with from that slide. -- **No content duplication** — the talking points reference the Marp slides - for visual context and the source markdown for full detail. +- **No content duplication** — the talking points reference the Marp + slides for visual context. ## Directory layout ``` docs/presentations/ ├── README.md ← this file -├── nova-autonomous-cloud-delivery.md ← Step 1: full source of truth (20 main slides + speaker notes) -├── nova-autonomous-cloud-delivery-marp.md ← Step 2: Marp deck (20 main + 1 appendix = 21 slides) -├── nova-autonomous-cloud-delivery.html ← Step 3: rendered HTML (committed, S&P-themed) -├── nova-autonomous-cloud-delivery.pptx ← Step 3: rendered PPTX (committed, S&P-themed) -├── nova-autonomous-cloud-delivery-talking-points.md ← Step 4: presenter cues (19 sections) +├── nova-autonomous-cloud-delivery-marp.md ← Step 1: sole source of truth (title + 20 main + 1 appendix = 22 slides + speaker notes + talking points) +├── nova-autonomous-cloud-delivery.html ← Step 2: rendered HTML (committed, S&P inline style, base64-inlined images) +├── nova-autonomous-cloud-delivery.pptx ← Step 2: MARP PPTX (image-of-slide, primary release attachment) +├── nova-autonomous-cloud-delivery-python.pptx ← Step 2: python-pptx (structured, editable) +├── nova-autonomous-cloud-delivery-talking-points.md ← Step 3: presenter cues (21 sections) └── assets/ - ├── nova-sp-theme.css ← S&P Global Energy Marp theme (all slide chrome) + ├── nova-sp-theme.css ← RETIRED from render — reference only (not loaded; live styling is the inline `style:` block) ├── puppeteer-config.json ← no-sandbox config for mmdc - ├── mmd/ ← mermaid source files (Step 2 input) - │ ├── sp-theme.json ← S&P Red/Black/White theme (mermaid-cli --configFile) + ├── mmd/ ← mermaid source files (Step 2 input) + │ ├── sp-theme.json ← S&P Red/Black/White theme (mermaid-cli --configFile) │ └── ... (per-slide .mmd files) - └── png/ ← rendered mermaid PNGs (committed, S&P-themed) + └── png/ ← rendered mermaid PNGs (committed, S&P-themed, 2x, transparent) ``` +## Tooling & scripts + +| Script | Purpose | +|---|---| +| `scripts/render_slides.sh` | End-to-end render: mermaid PNGs → MARP HTML + PPTX → base64-inlined HTML → python-pptx PPTX → stage all artifacts. Pinned `@marp-team/marp-cli@4.5.0` + `@mermaid-js/mermaid-cli@11.16.0`. | +| `scripts/inline_images.py` | Rewrites the rendered HTML to base64-embed every `assets/` image (self-contained HTML for redistribution). | +| `scripts/render_pptx.py` | Produces the structured, editable `*-python.pptx` (native text boxes, tables, pictures, italic benefit callouts) via `python-pptx`. | +| `scripts/attach_release_asset.py` | Attaches the MARP PPTX to the phase's release. | + +| Dependency | Where declared | Purpose | +|---|---|---| +| `@marp-team/marp-cli@4.5.0` | `scripts/render_slides.sh` (pinned) | Marp → HTML + PPTX | +| `@mermaid-js/mermaid-cli@11.16.0` | `scripts/render_slides.sh` (pinned) | Mermaid → PNG | +| `python-pptx>=0.6.23` | `pyproject.toml` `[project.optional-dependencies] slides` | Structured PPTX (`pip install -e ".[slides]"`) | + ## Conventions -### Appendix structure +### Slide structure -Each Marp deck has **20 main slides + 1 appendix slide**. The main 20 are the -presentation; the appendix is for Q&A backup. (v1.22 split slides 3 and 8 -to relieve overflow, increasing the count from 18 to 20.) +Each Marp deck has **1 title slide + 20 main slides + 1 appendix slide = 22 +rendered slides** (21 `## ` sections + the H1 title slide). The main 20 +are the presentation; the appendix is for Q&A backup. (v1.22 split slides +3 and 8 to relieve overflow, increasing the main count from 18 to 20.) +- **Title slide** (H1): `` + `` + for the dark-background title style (S&P-red top border on black). - **Main slides** (1-20): the story arc — Problem → Solution → Proof → Roadmap + Ask. These are what the audience sees during the talk. -- **Appendix slide** (A1): the Metrics Glossary — detail-heavy reference for - Q&A. +- **Appendix slide** (A1): the Metrics Glossary — detail-heavy reference + for Q&A. ### Honesty framing Every capability claim in the deck is grounded, derived, or honestly -deferred with its blocking work named in plain language. Internal provenance -(decision IDs, requirement IDs, internal file paths) is kept out of the -audience-facing slides — those live in the `.ciagent/` files only. When in -doubt, check `.ciagent/ROADMAP.md` and the milestone status in -`.ciagent/PROJECT.md`. +deferred with its blocking work named in plain language. Internal +provenance (decision IDs, requirement IDs, internal file paths) is kept +out of the audience-facing slide bodies — those live in the `.ciagent/` +files only (and may appear inside `` speaker-note comments, +which Marp excludes from the rendered slide). When in doubt, check +`.ciagent/ROADMAP.md` and the milestone status in `.ciagent/PROJECT.md`. ### Audience @@ -156,76 +193,70 @@ Head of Infrastructure, Head of DevOps. The framing rules: outcome; the mechanism follows. - **Security, remediation velocity, reliability, lead time, observability, citizen developer** are the themes — not implementation details. -- **"Infrastructure operations become visible"** is the recurring theme across - the deck. +- **"Infrastructure operations become visible"** is the recurring theme + across the deck. ### Diagrams -Mermaid diagrams in the Step 1 source use the repo's existing `flowchart` -style (renders on GitHub/Pages). For the Marp deck (Step 2): +Mermaid diagrams are authored as `assets/mmd/*.mmd` source files and +rendered to PNG under `assets/png/`: -1. Extract the mermaid block into `assets/mmd/--.mmd`. +1. Author the mermaid block as `assets/mmd/--.mmd`. 2. Use **horizontal layouts** (`flowchart LR`) or **subgraph row-wrapping** for wide diagrams so the PNG fits a 16:9 slide without shrinking to illegibility. -3. Render with a 2x scale factor and transparent background for crisp slides. -4. Embed with `![w:1000](assets/png/.png)` (or `h:320` for tall images). +3. Render with a 2x scale factor and transparent background for crisp + slides (`scripts/render_slides.sh` does this with the S&P theme JSON). +4. Embed with `![w:1000](assets/png/.png)` (or `h:480 class:tall` + for tall images). +5. The render pipeline base64-inlines the PNGs into the committed HTML so + the HTML is self-contained. ## Build commands ### Prerequisites -- Node.js + npx (for `@marp-team/marp-cli` and `@mermaid-js/mermaid-cli`) -- A Chrome/Chromium binary (Marp PPTX export requires it) +- **Node.js + npx** (for `@marp-team/marp-cli` and `@mermaid-js/mermaid-cli`) +- **A Chrome/Chromium binary** (Marp PPTX export requires it) +- **Python 3.10+** with the `slides` extra: `pip install -e ".[slides]"` + (installs `python-pptx>=0.6.23`) This environment has a working Chromium at: `/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome` -### Render all mermaid diagrams to PNG - -```bash -cd docs/presentations/assets -for f in mmd/*.mmd; do - name=$(basename "$f" .mmd) - PUPPETEER_EXECUTABLE_PATH=/root/.cache/ms-playwright/chromium-1217/chrome-linux64/chrome \ - npx --yes @mermaid-js/mermaid-cli@latest \ - -i "$f" -o "png/$name.png" \ - -p puppeteer-config.json -s 2 -b transparent \ - --configFile mmd/sp-theme.json -done -``` - -### Render a Marp deck to HTML + PPTX (committed artifacts) +### Render the deck (HTML + dual PPTX + inlined images) ```bash bash scripts/render_slides.sh nova-autonomous-cloud-delivery ``` -This renders all mermaid PNGs, the HTML, and the PPTX, and stages them for -commit. The `--allow-local-files` flag is required so local PNG diagrams are -embedded. Both HTML and PPTX are committed to the repo; the PPTX is also +This renders all mermaid PNGs, the HTML (with base64-inlined images), the +MARP PPTX, and the python-pptx PPTX, and stages them for commit. Both +HTML and both PPTX files are committed to the repo; the MARP PPTX is also attached to the phase's release. ## Adding a new presentation -1. **Write the full markdown** as `.md` following the - `## Slide N — Title` + `> **Speaker notes:**` structure. This is the - source of truth. -2. **Extract any mermaid diagrams** into `assets/mmd/--.mmd` - and render them to `assets/png/` (command above). -3. **Synthesize the Marp deck** as `-marp.md` with frontmatter, - no speaker notes, embedded PNGs, and no badges. -4. **Render to HTML + PPTX** via `scripts/render_slides.sh ` and - commit both to `docs/presentations/`. -5. **Distill the talking points** as `-talking-points.md` — one - section per Marp slide, 3-6 talking point bullets + key takeaway, content - distilled from the source markdown (Step 1), indexed by the Marp deck - (Step 2) slide structure. -6. **Verify** the PPTX slide count and that media files are embedded: +1. **Author the Marp deck** as `-marp.md` — frontmatter + (`marp: true`, `theme: default`, `paginate: true`, `size: 16x9`, an + inline `style:` block with the S&P palette), `## Slide N — Title` + sections, `` + `` HTML + comments, and `
` callouts. This is the sole source + of truth. +2. **Author any mermaid diagrams** as `assets/mmd/--.mmd` + (Step 2 renders them to `assets/png/`). +3. **Render** via `bash scripts/render_slides.sh ` — this + produces the HTML (base64-inlined), the MARP PPTX, and the python-pptx + PPTX, and stages all of them (plus the PNGs) for commit. +4. **Distill the talking points** as `-talking-points.md` — one + section per Marp slide, 3-6 talking point bullets + key takeaway, + content distilled from the Marp deck's `` + comments, indexed by the Marp deck slide structure. +5. **Verify** the PPTX slide count and that media files are embedded: ```bash python3 -c " import zipfile, re - with zipfile.ZipFile('.pptx') as z: + with zipfile.ZipFile('docs/presentations/.pptx') as z: slides = [n for n in z.namelist() if re.match(r'ppt/slides/slide\d+\.xml$', n)] media = [n for n in z.namelist() if n.startswith('ppt/media/')] print(f'{len(slides)} slides, {len(media)} media files') @@ -234,16 +265,17 @@ attached to the phase's release. ## Current decks -| Deck | Source of truth (Step 1) | Marp deck (Step 2) | Rendered HTML + PPTX (Step 3) | Talking points (Step 4) | Slides | Audience | -|---|---|---|---|---|---|---| -| Nova — The Autonomous Cloud Delivery Platform | `nova-autonomous-cloud-delivery.md` | `nova-autonomous-cloud-delivery-marp.md` | `nova-autonomous-cloud-delivery.html` + `.pptx` (committed + release-attached) | `nova-autonomous-cloud-delivery-talking-points.md` | 20 main + 1 appendix (21) | CTO, Head of Cloud, Head of Infra, Head of DevOps | +| Deck | Source of truth (Step 1) | Rendered HTML + dual PPTX (Step 2) | Talking points (Step 3) | Slides | Audience | +|---|---|---|---|---|---| +| Nova — The Autonomous Cloud Delivery Platform | `nova-autonomous-cloud-delivery-marp.md` | `nova-autonomous-cloud-delivery.html` (inlined) + `nova-autonomous-cloud-delivery.pptx` (MARP, release-attached) + `nova-autonomous-cloud-delivery-python.pptx` (structured) | `nova-autonomous-cloud-delivery-talking-points.md` | title + 20 main + 1 appendix (22) | CTO, Head of Cloud, Head of Infra, Head of DevOps | -> **v1.21:** the deck was renamed from "No-Humans Infrastructure Platform" -> to "Autonomous Cloud Delivery Platform" (professional framing; conveys -> autonomy without the provocative wording). The narrative restructured to -> a 4-beat arc (Problem → Solution → Proof → Roadmap + Ask). Internal -> provenance (decision IDs, requirement IDs, file paths) removed from -> audience-facing slides. Maturity badges removed. The RACI matrix expanded -> to four roles (Quality Engineering + SRE). The Atelier slide split into -> two. The pipeline hardened: Checkov on static code before the plan; -> Wiz-or-Checkov on the plan (never both). \ No newline at end of file +> **v1.23:** the slide creation process collapsed from 4 steps to 3 — the +> plain `.md` was deleted; `-marp.md` is now the +> sole source of truth. The standalone `nova-sp-theme.css` was retired +> from render (the live styling is the inline `style:` block in the +> `-marp.md` frontmatter; the CSS file is retained as a reference only). +> Speaker notes moved from blockquotes into `` +> HTML comments. Benefit callouts moved from `**Benefit:**` prefixes to +> `
`. The render pipeline now produces a dual-PPTX +> output (MARP image-of-slide + python-pptx structured) and base64-inlines +> all images into the committed HTML. \ No newline at end of file diff --git a/tests/test_pptx_generator.py b/tests/test_pptx_generator.py new file mode 100644 index 0000000..c1fc776 --- /dev/null +++ b/tests/test_pptx_generator.py @@ -0,0 +1,145 @@ +"""REQ-274: tests for `scripts/render_pptx.py` — the structured, editable +python-pptx deck produced alongside the MARP-rendered PPTX. + +The python-pptx deck is a native OOXML presentation: real text boxes, +native tables, embedded pictures, and italic benefit callouts. These +tests are offline (no AWS, no network) and assert the structural +properties of the committed `*-python.pptx` artifact. +""" +from pathlib import Path +from typing import cast + +import pytest +from pptx import Presentation +from pptx.enum.shapes import MSO_SHAPE_TYPE +from pptx.presentation import Presentation as PresentationT +from pptx.shapes.autoshape import Shape + +ROOT = Path(__file__).resolve().parent.parent +PRESENTATIONS = ROOT / "docs" / "presentations" +MARPT_DECK = PRESENTATIONS / "nova-autonomous-cloud-delivery-marp.md" +PYTHON_PPTX = PRESENTATIONS / "nova-autonomous-cloud-delivery-python.pptx" + +# Title slide + 20 main slides + 1 appendix slide. +EXPECTED_SLIDE_COUNT = 22 + + +@pytest.fixture(scope="module") +def prs() -> PresentationT: + """Load the committed python-pptx deck once for the whole module.""" + assert PYTHON_PPTX.is_file(), f"python-pptx PPTX not found: {PYTHON_PPTX}" + return Presentation(str(PYTHON_PPTX)) + + +def _slide_titles(prs: PresentationT) -> list[str]: + """Return the first non-empty text-frame line per slide (the title).""" + titles: list[str] = [] + for slide in prs.slides: + for shape in slide.shapes: + if not shape.has_text_frame: + continue + text = cast(Shape, shape).text_frame.text.strip() + if not text: + continue + # The title is the first non-empty line of the first non-empty + # text frame we find on the slide. + first_line = text.split("\n")[0].strip() + if first_line: + titles.append(first_line) + break + else: + titles.append("") + return titles + + +def test_slide_count(prs: PresentationT): + """REQ-269/274: the python-pptx deck has 22 slides + (title + 20 main + 1 appendix).""" + assert len(prs.slides) == EXPECTED_SLIDE_COUNT, \ + f"expected {EXPECTED_SLIDE_COUNT} slides, got {len(prs.slides)}" + + +def test_title_slide_colors(prs: PresentationT): + """REQ-269: the title slide (slide 0) has a solid-filled background + shape carrying the S&P Red (#D6002A) brand color (the title slide is + a red-bar-on-black layout).""" + title_slide = prs.slides[0] + red_found = False + black_found = False + for shape in title_slide.shapes: + fill = getattr(shape, "fill", None) + if fill is None: + continue + try: + if fill.type != 1: # MSO_FILL.SOLID + continue + except Exception: + continue + rgb = str(fill.fore_color.rgb).upper() + if rgb == "D6002A": + red_found = True + if rgb == "1B1B1B": + black_found = True + assert red_found, \ + "title slide has no solid-fill shape with S&P Red (#D6002A)" + + +def test_expected_slide_titles(prs: PresentationT): + """REQ-269/274: spot-check that key slide titles match the markdown + deck (The Problem, Nova's Vision, Recap + Ask).""" + titles = _slide_titles(prs) + # Build a flat lowercase concatenation for substring checks. + flat = " | ".join(titles).lower() + expected = [ + "the problem", + "nova's vision", + "recap + ask", + ] + missing = [t for t in expected if t not in flat] + assert not missing, \ + f"missing expected slide titles in python-pptx deck: {missing}; " \ + f"found titles: {titles}" + + +def test_table_rendering(prs: PresentationT): + """REQ-269: a slide with a table (the RACI slide) has a native PPTX + table shape (GraphicFrame with has_table=True).""" + table_slides = [] + for idx, slide in enumerate(prs.slides): + for shape in slide.shapes: + if shape.shape_type == MSO_SHAPE_TYPE.TABLE or getattr( + shape, "has_table", False + ): + table_slides.append(idx) + break + assert table_slides, \ + "no slide in the python-pptx deck has a native PPTX table shape" + + +def test_image_embedding(prs: PresentationT): + """REQ-269: a slide with an image (the Platform Pipeline slide) + has a native PPTX picture shape.""" + picture_slides = [] + for idx, slide in enumerate(prs.slides): + for shape in slide.shapes: + if shape.shape_type == MSO_SHAPE_TYPE.PICTURE: + picture_slides.append(idx) + break + assert picture_slides, \ + "no slide in the python-pptx deck has a native PPTX picture shape" + + +def test_benefit_callout_present(prs: PresentationT): + """REQ-269/274: at least one slide has an italic text run (the + benefit callout, rendered as italic body text by render_pptx.py).""" + italic_runs = 0 + for slide in prs.slides: + for shape in slide.shapes: + if not shape.has_text_frame: + continue + for paragraph in cast(Shape, shape).text_frame.paragraphs: + for run in paragraph.runs: + if run.font.italic and run.text.strip(): + italic_runs += 1 + assert italic_runs > 0, \ + "no italic text runs found in the python-pptx deck (benefit callout)" \ No newline at end of file diff --git a/tests/test_slides_pipeline.py b/tests/test_slides_pipeline.py index cda8202..34fdc8d 100644 --- a/tests/test_slides_pipeline.py +++ b/tests/test_slides_pipeline.py @@ -1,21 +1,28 @@ -"""REQ-239..243 (v1.20) + REQ-245,251,252 (v1.21): S&P theme + slide render -pipeline + deck-refinement tests. +"""REQ-239..243 (v1.20) + REQ-245,251,252 (v1.21/22) + REQ-273..275 (v1.23): +S&P theme + slide render pipeline + deck-refinement tests. v1.20 validates: - - The Marp deck frontmatter references nova-sp-theme.css - - The CSS file contains the S&P colors (#D6002A, #1B1B1B) - The mermaid theme JSON contains the S&P colors - Every .mmd has a corresponding .png - The render_slides.sh script exists and is executable - The CI workflow file exists -v1.21 adds (REQ-245,251,252): +v1.21/22 adds (REQ-245,251,252): - Deck renamed to nova-autonomous-cloud-delivery* - No maturity badges in the Marp deck - No version in the Marp footer/title slide - - 20 main + 1 appendix slides (v1.22 split slides 3+8 to relieve overflow) + - 20 main + 1 appendix slides - No D-###/REQ-###/internal .py paths in audience-facing slides - - Title is "Nova — The Autonomous Cloud Delivery Platform" + +v1.23 (REQ-273,274,275) — single-document + dual-PPTX + image-inlining pipeline: + - The plain `.md` is gone; `*-marp.md` is the sole source of truth. + - Marp deck uses `theme: default` + an inline `style:` block (S&P colors). + - `nova-sp-theme.css` is RETAINED AS REFERENCE (not loaded at render). + - HTML has base64-inlined images (zero `src="assets/` references). + - A second PPTX (`*-python.pptx`) is produced by `scripts/render_pptx.py`. + - Speaker notes live as `` HTML comments. + - Benefit callouts use `
` (no `**Benefit:**` prefixes). + - The purged term "penetrate" is absent repo-wide. """ import re from pathlib import Path @@ -29,50 +36,216 @@ THEME_CSS = ASSETS / "nova-sp-theme.css" THEME_JSON = ASSETS / "mmd" / "sp-theme.json" MARP_DECK = PRESENTATIONS / "nova-autonomous-cloud-delivery-marp.md" SOURCE_MD = PRESENTATIONS / "nova-autonomous-cloud-delivery.md" +HTML = PRESENTATIONS / "nova-autonomous-cloud-delivery.html" +PYTHON_PPTX = PRESENTATIONS / "nova-autonomous-cloud-delivery-python.pptx" +MARP_PPTX = PRESENTATIONS / "nova-autonomous-cloud-delivery.pptx" RENDER_SCRIPT = ROOT / "scripts" / "render_slides.sh" SLIDES_WORKFLOW = ROOT / ".github" / "workflows" / "slides.yml" +def _frontmatter(text: str) -> str: + """Return the Marp frontmatter block (between the first two `---`).""" + fm_match = re.match(r'^---\n(.*?)\n---', text, re.DOTALL) + assert fm_match, "Marp frontmatter not found" + return fm_match.group(1) + + +# --- S&P theme reference + mermaid theme ------------------------------- + def test_sp_theme_css_exists(): - """REQ-239: nova-sp-theme.css exists.""" + """REQ-239: nova-sp-theme.css exists (retained as a reference).""" assert THEME_CSS.is_file(), f"theme CSS not found: {THEME_CSS}" +def test_nova_sp_theme_css_retained_as_reference(): + """REQ-274: nova-sp-theme.css is retained as a REFERENCE only and is + explicitly NOT loaded at render time (the live styling is the inline + `style:` block in the -marp.md frontmatter).""" + assert THEME_CSS.is_file(), f"theme CSS not found: {THEME_CSS}" + css = THEME_CSS.read_text() + assert "not loaded at render" in css.lower(), \ + "nova-sp-theme.css does not document itself as 'not loaded at render'" + + def test_sp_theme_css_has_snp_colors(): - """REQ-239: CSS contains S&P Red and Black.""" + """REQ-239: the reference CSS still carries S&P Red and Black.""" css = THEME_CSS.read_text() assert "#D6002A" in css, "S&P Red (#D6002A) missing from theme CSS" assert "#1B1B1B" in css, "S&P Black (#1B1B1B) missing from theme CSS" def test_sp_theme_json_has_snp_colors(): - """The mermaid theme JSON also has S&P colors.""" + """The mermaid theme JSON has S&P colors (mermaid PNGs are S&P-themed).""" json_text = THEME_JSON.read_text() assert "#D6002A" in json_text, "S&P Red missing from mermaid theme" assert "#1B1B1B" in json_text, "S&P Black missing from mermaid theme" -def test_marp_deck_uses_sp_theme(): - """REQ-239: Marp deck frontmatter references nova-sp-theme.css.""" +# --- Marp deck: theme + inline style ---------------------------------- + +def test_marp_deck_uses_default_theme(): + """REQ-274: the Marp deck frontmatter uses `theme: default` (not the + retired `theme: nova-sp`). S&P styling is delivered by the inline + `style:` block, not the standalone CSS.""" + frontmatter = _frontmatter(MARP_DECK.read_text()) + assert re.search(r"^theme:\s*default\s*$", frontmatter, re.MULTILINE), \ + "Marp deck does not set `theme: default` in the frontmatter" + assert "nova-sp" not in frontmatter, \ + "Marp deck still references the retired `nova-sp` theme" + + +def test_marp_deck_has_sp_inline_style(): + """REQ-274: the inline `style:` block carries the S&P properties + (#D6002A, #1B1B1B, and the `section.title` rule).""" + frontmatter = _frontmatter(MARP_DECK.read_text()) + assert "style:" in frontmatter, "frontmatter has no inline `style:` block" + # The inline style block extends past the frontmatter close in Marp + # (the `style:` value is a multi-line YAML literal). Read the whole + # deck so we capture the full style block. + deck = MARP_DECK.read_text() + assert "#D6002A" in deck, "inline style: block missing #D6002A" + assert "#1B1B1B" in deck, "inline style: block missing #1B1B1B" + assert "section.title" in deck, \ + "inline style: block missing the `section.title` rule" + + +def test_marp_deck_no_badges(): + """REQ-252: no maturity badges in the Marp deck.""" text = MARP_DECK.read_text() - # The frontmatter is between the first two --- - fm_match = re.match(r'^---\n(.*?)\n---', text, re.DOTALL) - assert fm_match, "Marp frontmatter not found" - frontmatter = fm_match.group(1) - assert "nova-sp" in frontmatter, \ - "Marp deck does not reference nova-sp theme" + assert "badge" not in text, "Marp deck still contains badge spans" -def test_marp_deck_not_using_default_theme(): - """The Marp deck must not use 'theme: default'.""" +def test_marp_deck_no_version_in_footer(): + """REQ-251: no version (v1.x) in the Marp frontmatter footer/header.""" + frontmatter = _frontmatter(MARP_DECK.read_text()) + assert not re.search(r"v1\.\d+", frontmatter), \ + f"Marp frontmatter still contains a version: {frontmatter}" + assert "Act %" not in frontmatter, \ + "Marp frontmatter still contains 'Act %{page}' artifact" + + +def test_marp_deck_title_slide_no_version_subtitle(): + """REQ-251: the title slide does not carry a version subtitle.""" text = MARP_DECK.read_text() - fm_match = re.match(r'^---\n(.*?)\n---', text, re.DOTALL) - assert fm_match, "Marp frontmatter not found" - frontmatter = fm_match.group(1) - assert "theme: default" not in frontmatter, \ - "Marp deck still uses 'theme: default' — should use nova-sp-theme.css" + after_fm = text.split("---\n", 2)[2] if text.startswith("---") else text + first_slide = after_fm.split("\n---\n")[0] + assert "v1.18" not in first_slide, \ + "Title slide still contains 'v1.18' subtitle" + assert "Citizen Developer & Production-Grade Guidance" not in first_slide, \ + "Title slide still contains the old version subtitle" +def test_marp_deck_title_is_autonomous_cloud_delivery(): + """REQ-245: the deck title is 'Nova — The Autonomous Cloud Delivery Platform'.""" + text = MARP_DECK.read_text() + assert "Autonomous Cloud Delivery Platform" in text, \ + "Deck title is not 'Autonomous Cloud Delivery Platform'" + assert "No-Humans Infrastructure Platform" not in text, \ + "Deck still carries the old 'No-Humans Infrastructure Platform' title" + + +def test_marp_deck_slide_count(): + """REQ-245/261: 20 main slides + 1 appendix = 21 slide sections + (22 rendered sections incl. the H1 title slide).""" + text = MARP_DECK.read_text() + main_slides = re.findall(r"^## Slide ", text, re.MULTILINE) + appendix_slides = re.findall(r"^## Appendix ", text, re.MULTILINE) + assert len(main_slides) == 20, \ + f"expected 20 main slides, found {len(main_slides)}" + assert len(appendix_slides) == 1, \ + f"expected 1 appendix slide, found {len(appendix_slides)}" + + +def test_marp_deck_no_internal_citations(): + """REQ-252: no D-### decision IDs, REQ-### requirement IDs, or internal + .py file paths in the audience-facing Marp deck SLIDE BODIES. Internal + provenance is allowed inside `` HTML comments (speaker + notes / talking points), which Marp excludes from the rendered slide.""" + text = MARP_DECK.read_text() + # Strip HTML comments (speaker notes + talking points) before checking. + body = re.sub(r"", "", text, flags=re.DOTALL) + assert not re.search(r"\bD-\d{3}\b", body), \ + "Marp deck slide body contains D-### decision IDs" + assert not re.search(r"\bREQ-\d{3}\b", body), \ + "Marp deck slide body contains REQ-### requirement IDs" + assert not re.search(r"\b(outbox_writer|confidence_signal|hitl_gates|" + r"attestation_matrix|checkov_adapter|infracost_adapter|" + r"contract_resolver|run_platform)\.py\b", body), \ + "Marp deck slide body contains internal .py file paths" + + +# --- Speaker notes + benefit callouts (REQ-274) ---------------------- + +def test_speaker_notes_as_html_comments(): + """REQ-274: speaker notes are embedded as `` + HTML comments (Marp excludes HTML comments from the rendered slide; + the comments are for authors/presenters). Expect >= 20 (one per main + slide) + the appendix slide.""" + text = MARP_DECK.read_text() + count = len(re.findall(r"