docs(P00): research — v1.23 Nova Deck Cleanup & Python PPTX

10 findings grounding the v1.23 milestone plan:
- Marp default theme + inline style block (exact CSS from ref deck)
- HTML passthrough confirmed; python-pptx API mapped; stdlib image
  inlining sufficient; 12 tests need updating; attach script +
  slides.yml + README structure documented; persona roster (same as
  v1.22); 5 pitfalls identified.

---ci---
phase: 0
milestone: v1.23
status: research
decisions:
  - id: D-163
    decision: Inline Marp style block is lead-developer territory (not frontend-engineer)
    rationale: Marp frontmatter CSS is a static stylesheet, not a React/Next.js component system (D-148 precedent from v1.22)
    confidence: 0.95
    alternatives: [frontend-engineer owns CSS, custom slides-engineer persona]
  - id: D-164
    decision: No new personas for v1.23
    rationale: Work splits cleanly into lead-developer (markdown+CSS+README+metadata) and backend-engineer (Python+bash+tests+CI); python-pptx is backend
    confidence: 0.90
    alternatives: [custom docs/deck persona, pptx-engineer persona]
---/ci---
This commit is contained in:
Jon Chery
2026-08-11 23:54:47 +00:00
parent 0d22b89a7b
commit f496dd9c24
2 changed files with 871 additions and 137 deletions
+169 -137
View File
@@ -1,32 +1,174 @@
--- ---
project: acdl project: acdl
milestone: v1.22 milestone: v1.23
generated_at: 2026-08-11 generated_at: 2026-08-11
generator: lead-developer generator: lead-developer
verification_toolchain: verification_toolchain:
typecheck: "python3 -m py_compile tests/test_slides_pipeline.py" typecheck: "python3 -m py_compile scripts/inline_images.py scripts/render_pptx.py tests/test_slides_pipeline.py"
test: "pytest tests/test_slides_pipeline.py # REQ-254..262" test: "pytest tests/test_slides_pipeline.py # REQ-263..275"
build: "bash scripts/render_slides.sh nova-autonomous-cloud-delivery # HTML + PPTX" build: "bash scripts/render_slides.sh nova-autonomous-cloud-delivery # HTML + Marp PPTX; then python3 scripts/render_pptx.py # structured PPTX"
note: | note: |
v1.22 is the Nova Deck Layout Fix — a docs-only NFR milestone. Two v1.23 is the Nova Deck Cleanup & Python PPTX — a docs/render/test
active personas: lead-developer (theme CSS + deck markdown + talking NFR milestone. Two active personas: lead-developer (deck markdown
points + README + .ciagent metadata), backend-engineer (render scripts consolidation + inline Marp style CSS + README + .ciagent metadata),
+ tests). frontend-engineer stays deactivated (decks are markdown = backend-engineer (Python scripts inline_images.py + render_pptx.py
lead-developer territory, per v1.17/v1.18 precedent). No data-engineer via python-pptx + render_slides.sh updates + tests + CI YAML +
(no schema/DB changes). No new personas (the work is CSS + bash + attach_release_asset.py extension). frontend-engineer stays
markdown + pytest, all within the two active personas' range). deactivated (decks are markdown + Marp CSS = lead-developer
territory, per v1.17/v1.18/v1.22 precedent). No data-engineer (no
schema/DB changes). No new personas (the work splits cleanly into
narrative+CSS+docs and Python+bash+tests+CI).
--- ---
# ACDL — Persona Roster (v1.22 Nova Deck Layout Fix) # ACDL — Persona Roster (v1.23 Nova Deck Cleanup & Python PPTX)
> v1.22 roster. Two active personas + one deactivated. This is a docs- > v1.23 roster. Two active personas + two deactivated. This is a
> only NFR milestone: the work is theme CSS, render scripts, mermaid > docs/render/test NFR milestone: the work is deck markdown
> diagrams, deck markdown, and tests. frontend-engineer stays > consolidation, Marp inline `style:` CSS (reverting from the standalone
> deactivated (decks are markdown = lead-developer territory, per > `nova-sp-theme.css` to `theme: default` + inline block), two Python
> v1.17/v1.18 precedent). No data-engineer (no schema/DB changes). > render scripts (`inline_images.py` stdlib image inlining +
> `render_pptx.py` structured PPTX via python-pptx), test updates, CI
> YAML, README rewrite, and the `attach_release_asset.py` extension to
> dual-PPTX. frontend-engineer stays deactivated (Marp CSS is a static
> stylesheet, not a React/Next.js component system — D-148 precedent).
> No data-engineer (no schema/DB/ORM changes).
## Active personas ## Active personas
### lead-developer
- **Domain:** coordination + deck content + inline CSS
- **Active:** true
- **Phase-specific:** false
- **Frameworks:** [] (no framework — owns process + narrative + Marp inline CSS + markdown + README)
- **Constraints:** ["pragmatic", "battle-tested defaults", "no fabrication (NORTH_STAR honesty model)", "do not change the 4-beat arc", "do not re-introduce badges/version/internal citations", "port the reference deck's exact inline style CSS (the clean look)"]
- **Territory:**
- `docs/presentations/nova-autonomous-cloud-delivery-marp.md` (REQ-263..275 — deck consolidation: merge plain .md into -marp.md, trim word count, remove "penetrate")
- `docs/presentations/nova-autonomous-cloud-delivery.md` (DELETE — consolidated into -marp.md)
- `docs/presentations/nova-autonomous-cloud-delivery-talking-points.md` (sync to final slide structure)
- `docs/presentations/README.md` (rewrite 4-step → 3-step process for single-document + dual-PPTX pipeline)
- `docs/presentations/assets/nova-sp-theme.css` (RETIRE from render; keep as reference file)
- `.ciagent/**` (PROJECT, ROADMAP, REQUIREMENTS, RESEARCH, PLAN, GRILL, PERSONAS, REVIEW, CHECKPOINT)
- **Reason:** Owns the deck markdown consolidation (plain `.md` +
`-marp.md` → single `-marp.md` with speaker notes as HTML comments),
the inline `style:` CSS block (porting the reference deck's exact
CSS — the "clean look"), the word-count trim, the "penetrate"
removal, the talking-points sync, the README rewrite, and all
CIAgent metadata. Is the only persona that touches `.ciagent/**` and
the deck markdown/CSS. The inline `style:` block is a Marp
frontmatter stylesheet, not a frontend component system (D-148
precedent from v1.22).
- **Phase-specific flag:** none (active for all of P0P-final).
### backend-engineer
- **Domain:** Python render scripts + bash + tests + CI
- **Active:** true
- **Phase-specific:** false
- **Frameworks:** ["python-pptx", "bash", "pytest", "marp-cli", "mermaid-cli", "base64/re/mimetypes (stdlib)"]
- **Constraints:** ["pin CLI versions (no @latest in workflows-src/slides.yml — currently unpinned, must fix)", "python-pptx>=0.6.23 minimum", "stdlib-only for inline_images.py (no external image lib)", "tests must catch the theme-default + inline-style regression (the gap that let v1.22's standalone-theme drift through)", "no raw curl with shell-env tokens"]
- **Territory:**
- `scripts/inline_images.py` (NEW — stdlib base64 image inlining into rendered HTML)
- `scripts/render_pptx.py` (NEW — structured editable PPTX via python-pptx)
- `scripts/render_slides.sh` (update: drop --theme nova-sp-theme.css; add inline_images.py + render_pptx.py invocations)
- `scripts/attach_release_asset.py` (extend to accept multiple file paths: both .pptx + -python.pptx)
- `tests/test_slides_pipeline.py` (REQ-262 successor — invert theme tests, retarget CSS tests to inline style block, delete source-md tests, update slide count)
- `workflows-src/slides.yml` (add python-pptx install step; pin @latest@4.5.0/@11.16.0; add -python.pptx to commit list)
- `pyproject.toml` (add python-pptx>=0.6.23 to [project.optional-dependencies] test)
- **Reason:** Owns the Python render scripts (`inline_images.py` is
stdlib regex + base64; `render_pptx.py` is python-pptx — a backend
Python library, not a frontend framework). The render_slides.sh
updates (drop `--theme`, add the two Python script invocations) and
the CI YAML (add python-pptx install, pin versions, add
-python.pptx to commit list) are backend/scripting tasks. The test
suite updates (invert the theme-default test, retarget CSS tests to
the inline `style:` block, delete the source-md tests) are the gap
that let v1.22's standalone-theme drift through — backend-engineer
owns closing it. python-pptx is new to v1.23; backend-engineer's
frameworks list gains it.
- **Phase-specific flag:** none (active for P2 scripts, P5 tests; light
touch on P0/P-final).
## Deactivated personas
### frontend-engineer
- **Active:** false
- **Domain:** frontend
- **Frameworks:** ["react", "next.js"] (inert — no territory)
- **Constraints:** ["component-first", "server-components", "minimal-client-js"] (inert)
- **Territory:** [] (no territory in v1.23)
- **Reason:** v1.23 has no frontend; decks are markdown + Marp inline
CSS (lead-developer territory); deactivated per PERSONAS.md
v1.17/v1.18/v1.22 precedent. The inline `style:` block is a Marp
frontmatter stylesheet (CSS rules in the markdown frontmatter), not
a React/Next.js component system — it stays lead-developer
territory. No reactivation trigger.
### data-engineer
- **Active:** false
- **Domain:** data
- **Frameworks:** [] (inert)
- **Constraints:** [] (inert)
- **Territory:** [] (no territory in v1.23)
- **Reason:** v1.23 has no schema/DB/ORM changes. The milestone is
docs + Python scripts + bash + tests + CI only. No reactivation
trigger.
## Roster decisions
### D-163 (0.95): Inline Marp `style:` block is lead-developer territory, not frontend-engineer
The inline `style:` block in the Marp frontmatter is a static CSS
stylesheet (a block of CSS rules in the markdown frontmatter, applied
by Marp to the rendered slides), not a React/Next.js component system.
The v1.17/v1.18/v1.22 precedent (decks are markdown = lead-developer
territory; the theme CSS is a Marp stylesheet, not a frontend framework
— D-148) extends to the inline `style:` block. frontend-engineer's
frameworks (react, next.js) are irrelevant to Marp frontmatter CSS.
**Decision:** inline `style:` block stays lead-developer territory.
Confidence 0.95 — the only counter-argument is that CSS is "frontend,"
but Marp frontmatter CSS is a static stylesheet authored in the
markdown source, not a component system.
### D-164 (0.90): No new personas for v1.23
The work is markdown + inline CSS + Python scripts + bash + tests + CI
YAML + README. All of this is within the two active personas' range
(lead-developer: markdown + CSS + README + metadata; backend-engineer:
Python + bash + tests + CI). Creating a separate "slides-engineer" or
"pptx-engineer" persona would fragment ownership of the deck markdown
(lead) and the render scripts (backend). The python-pptx work is
unambiguously backend (a Python library + script), even though the
slide layout decisions within `render_pptx.py` are co-owned with lead-
developer. **Decision:** no new personas. Confidence 0.90 — follows
v1.22 D-149 precedent.
### Territory-overlap resolution (co-ownership)
| Path | Primary | Co-owner | Why |
|------|---------|----------|-----|
| `scripts/render_pptx.py` | backend-engineer (Python code + python-pptx API) | lead-developer (slide layout decisions: which image where, font sizes, bullet structure) | The script is backend (Python); the slide-by-slide layout decisions within it are design decisions that reflect lead-developer's deck ownership. `warn` enforcement — cross-territory edits logged, not blocked. |
| `scripts/render_slides.sh` | backend-engineer (bash script) | lead-developer (the inline `style:` block content it renders) | The script is backend; the CSS it renders is lead-developer's. |
| `tests/test_slides_pipeline.py` | backend-engineer (test code) | lead-developer (assertions reflect deck structure + inline CSS) | The test code is backend; the assertions (slide count, inline style rules, theme-default) reflect lead-developer's deck/CSS decisions. |
| `docs/presentations/README.md` | lead-developer (process narrative) | backend-engineer (build commands + render script invocations) | The process narrative is lead; the build commands + script names are backend. |
## Domain priority (v1.23)
`lead-developer → backend-engineer`
Rationale: the deck markdown consolidation + inline CSS (lead) is the
binding constraint — the render scripts (backend) can't be finalized
until the deck structure is stable (slide count, inline style). The
inline `style:` block must be ported + verified before the render
pipeline (HTML + PPTX) is re-run. Backend (scripts + tests + CI)
follows once the deck + CSS are settled. The dual-PPTX pipeline is
wired last, once the deck renders clean with the inline style.
---
## Historical rosters
<details>
<summary>v1.22 roster (Nova Deck Layout Fix) — superseded by v1.23</summary>
### Active personas (v1.22)
### lead-developer ### lead-developer
- **Domain:** coordination + deck content - **Domain:** coordination + deck content
- **Active:** true - **Active:** true
@@ -65,61 +207,13 @@ verification_toolchain:
backend/scripting tasks. backend/scripting tasks.
- **Phase-specific flag:** none (active for P2, P5; light touch on P0/P6). - **Phase-specific flag:** none (active for P2, P5; light touch on P0/P6).
## Deactivated personas ### D-148 (v1.22): Theme CSS is lead-developer territory, not frontend-engineer
### D-149 (v1.22): No new personas for v1.22
### frontend-engineer </details>
- **Active:** false
- **Domain:** frontend
- **Frameworks:** ["react", "next.js"] (inert — no territory)
- **Constraints:** ["component-first", "server-components", "minimal-client-js"] (inert)
- **Territory:** [] (no territory in v1.22)
- **Reason:** v1.22 has no frontend; decks are markdown (lead-developer
territory); deactivated per PERSONAS.md v1.17/v1.18 precedent. The
theme CSS is a Marp stylesheet, not a React/Next.js component system
— it stays lead-developer territory. No reactivation trigger.
### data-engineer
- **Active:** false
- **Domain:** data
- **Frameworks:** [] (inert)
- **Constraints:** [] (inert)
- **Territory:** [] (no territory in v1.22)
- **Reason:** v1.22 has no schema/DB/ORM changes. The milestone is
docs + scripts + tests only. No reactivation trigger.
## Roster decisions
### D-148 (0.95): Theme CSS is lead-developer territory, not frontend-engineer
The `nova-sp-theme.css` is a Marp stylesheet (CSS for a markdown-to-
slide renderer), not a React/Next.js component system. The v1.17/v1.18
precedent (decks are markdown = lead-developer territory) extends to
the deck's CSS theme. frontend-engineer's frameworks (react, next.js)
are irrelevant to Marp CSS. **Decision:** theme CSS stays lead-developer
territory. Confidence 0.95 — the only counter-argument is that CSS is
"frontend," but Marp CSS is a static stylesheet, not a component system.
### D-149 (0.9): No new personas for v1.22
The work is CSS + bash + markdown + mermaid + pytest. All of this is
within the two active personas' range (lead-developer: CSS + markdown +
mermaid; backend-engineer: bash + pytest). Creating a separate "css-
engineer" or "slides-engineer" persona would fragment ownership of the
theme CSS + deck markdown (both lead-developer) and the render scripts
+ tests (both backend-engineer). **Decision:** no new personas.
Confidence 0.9.
### Territory-overlap resolution (co-ownership)
| Path | Primary | Co-owner | Why |
|------|---------|----------|-----|
| `docs/presentations/assets/mmd/*.mmd` | lead-developer (mermaid re-layout) | backend-engineer (re-render via render_slides.sh) | The .mmd content is lead-developer (diagram narrative); the PNG re-render is backend-engineer (script invocation). |
| `tests/test_slides_pipeline.py` | backend-engineer (test code) | lead-developer (assertions reflect deck structure) | The test code is backend; the assertions (slide count, theme rules, aspect ratios) reflect lead-developer's deck/theme decisions. |
---
## Historical rosters
<details> <details>
<summary>v1.18 roster (Citizen Developer & Production-Grade Guidance) — superseded by v1.22</summary> <summary>v1.18 roster (Citizen Developer & Production-Grade Guidance) — superseded</summary>
### Active personas (v1.18) ### Active personas (v1.18)
@@ -139,94 +233,32 @@ Confidence 0.9.
- `docs/scope.md` (REQ-216) - `docs/scope.md` (REQ-216)
- `docs/skills.md` (REQ-222 — the index page, not the skill files themselves) - `docs/skills.md` (REQ-222 — the index page, not the skill files themselves)
- `docs/submission-readiness.md` (REQ-219 — citizen-developer-facing copy; co-owned with backend-engineer for the reason-code catalog) - `docs/submission-readiness.md` (REQ-219 — citizen-developer-facing copy; co-owned with backend-engineer for the reason-code catalog)
- **Reason:** Owns CIAgent metadata, the milestone narrative, the RACI +
PDLC-scope statements (REQ-215/216), the deck (21 slides, S&P theme
regression check vs P1, CAP-024), the skills index page (REQ-222), and
the citizen-developer-facing submission-readiness doc (REQ-219). Is
the only persona that touches `.ciagent/**` and the deck markdown.
- **Phase-specific flag:** none (active for all of P0P7).
### backend-engineer ### backend-engineer
- **Domain:** backend - **Domain:** backend
- **Active:** true - **Active:** true
- **Phase-specific:** false - **Phase-specific:** false
- **Frameworks:** ["mcp (Python SDK v2)", "pydantic", "jsonschema", "urllib"] - **Frameworks:** ["mcp (Python SDK v2)", "pydantic", "jsonschema", "urllib"]
- **Constraints:** ["api-first", "strict-typing", "plugin-registry extensible (D-140)", "stdio now / HTTP-ready (D-135)", "no stack traces to citizen developers (REQ-218)"]
- **Territory:** - **Territory:**
- `mcp/atelier/server.py` (REQ-223) - `mcp/atelier/server.py` (REQ-223)
- `mcp/atelier/plugins/**/*.py` (REQ-223 — principles.py, validation.py) - `mcp/atelier/plugins/**/*.py` (REQ-223 — principles.py, validation.py)
- `mcp/atelier/vendor/**` (REQ-224 — vendored Atelier snapshot) - `mcp/atelier/vendor/**` (REQ-224 — vendored Atelier snapshot)
- `mcp/atelier/VERSION.md` + `mcp/atelier/README.md` (REQ-224) - `mcp/atelier/VERSION.md` + `mcp/atelier/README.md` (REQ-224)
- `scripts/update_atelier_vendor.sh` (REQ-224) - `scripts/update_atelier_vendor.sh` (REQ-224)
- `core/submission_readiness.py` (REQ-218 — the validator, invoked as `contract_ingestor.py --check-readiness`) - `core/submission_readiness.py` (REQ-218)
- `scripts/render_deck.sh` (REQ-228 — HTML + PPTX render) - `scripts/render_deck.sh` (REQ-228)
- `scripts/attach_release_asset.py` (REQ-228 — Gitea release asset upload) - `scripts/attach_release_asset.py` (REQ-228)
- `tests/test_atelier_mcp.py` (REQ-225) - `tests/test_atelier_mcp.py` (REQ-225)
- `tests/test_submission_readiness.py` (REQ-220) - `tests/test_submission_readiness.py` (REQ-220)
- `docs/submission-readiness.md` (REQ-219 — reason-code catalog section; co-owned with lead-developer for the narrative)
- **Reason:** Owns the MCP server (plugin-registry, stdio, vendored
Atelier), the submission-readiness validator (extends
`contract_ingestor.py --check-readiness`, D-133), the render/attach
scripts (D-142 trigger), and the two new test files. The MCP
plugin-registry (D-140) is a backend pattern — no separate
mcp-engineer persona is created; backend-engineer owns it.
- **Phase-specific flag:** none (active for P1 deck-render, P3 validator,
P5 MCP server, P6 scripts).
### data-engineer ### data-engineer
- **Domain:** data - **Domain:** data
- **Active:** true - **Active:** true
- **Phase-specific:** false
- **Frameworks:** ["jsonschema", "dynamodb (item shape)"] - **Frameworks:** ["jsonschema", "dynamodb (item shape)"]
- **Constraints:** ["schema-first", "superset-gate NOT duplicate (PROJECT.md hard constraint)", "W3.E per-env mandatory table is the source of truth"]
- **Territory:** - **Territory:**
- `schemas/**` (REQ-217`submission-readiness.schema.json` is the new schema; existing schemas untouched) - `schemas/**` (REQ-217)
- `core/lambda/contract_ingestor.py` (the `--check-readiness` subcommand wiring, D-133 — the validator is in `core/submission_readiness.py` but the ingestor dispatches to it; co-owned with backend-engineer) - `core/lambda/contract_ingestor.py` (co-owned with backend-engineer)
- **Reason:** Owns the submission-readiness JSON Schema (REQ-217) — it
is a schema artifact, data-engineer territory. The schema is a
*superset gate above* `contract.schema.json`, not a duplicate (it
references contract fields, does not redefine them). The
per-env-mandatory table comes from W3.E (the locked decision). The
ingestor wiring is co-owned with backend-engineer (the dispatch point
is backend; the schema it validates against is data).
- **Phase-specific flag:** none (active for P3 schema + ingestor wiring).
### Deactivated personas (v1.18) ### D-143 (v1.18): Fold mcp-engineer into backend-engineer
### frontend-engineer
- **Active:** false
- **Domain:** frontend
- **Frameworks:** ["react", "next.js"] (inert — no territory)
- **Constraints:** ["component-first", "server-components", "minimal-client-js"] (inert)
- **Territory:** [] (no territory in v1.18)
- **Reason:** v1.18 has no frontend; decks are markdown (lead-developer
territory); deactivated per PERSONAS.md v1.17 precedent. v1.18's
observability stays PowerBI / external (Out of Scope: "A Nova-built
frontend / dashboard"). The MCP server exposes tools to an AI agent,
not a web UI. No reactivation trigger in this milestone.
### Roster decisions (v1.18)
### D-143 (0.90): Fold mcp-engineer into backend-engineer
The MCP plugin-registry (D-140: `plugins/<name>.py register(mcp)`) is a
backend code pattern — Python modules, type hints, stdio transport,
urllib for the Gitea asset API. It shares nothing with the data domain
(schemas/DynamoDB) and is not a new engineering discipline. Creating a
separate `mcp-engineer` persona would fragment ownership of the server +
its tests + the render/attach scripts (all backend). **Decision:** fold
into backend-engineer. backend-engineer's `frameworks` list gains
`mcp (Python SDK v2)`. Confidence 0.90 — the only counter-argument is
that MCP is a distinct protocol skill, but the SDK v2 API surface
(`@mcp.tool()` + type hints) is small and well within backend-engineer's
range (it's the same Pydantic/FastAPI-style pattern the persona already
knows).
### Territory-overlap resolution (v1.18)
| Path | Primary | Co-owner | Why |
|------|---------|----------|-----|
| `docs/submission-readiness.md` | lead-developer (narrative + examples) | backend-engineer (reason-code catalog, REQ-218 codes) | The doc is citizen-developer-facing copy (lead) but the reason-code catalog (MISSING_TAGS, ENV_MISSING_MANDATORY, AGENTIC_MISSING_INTENT, MISSING_APP_SOURCE, POLICY_PRECONDITION_MISSING) is backend (it mirrors the validator's return codes). |
| `core/lambda/contract_ingestor.py` | backend-engineer (dispatch wiring) | data-engineer (the schema it validates against) | D-133 places the `--check-readiness` subcommand on the ingestor (backend dispatch), but the readiness schema it loads is data-engineer territory. |
| `schemas/submission-readiness.schema.json` | data-engineer (schema artifact) | backend-engineer (the validator must match it) | The schema is data-engineer's; the validator (REQ-218) is backend-engineer's and must stay in sync with it. |
</details> </details>
+702
View File
@@ -2568,3 +2568,705 @@ All 5 CLARIFY decisions are grounded in these findings:
- Re-layout to LR (FINDING 3 — TB produced 0.63 aspect) - Re-layout to LR (FINDING 3 — TB produced 0.63 aspect)
- Delete render_deck.sh (FINDING 5 — stale, unthemed) - Delete render_deck.sh (FINDING 5 — stale, unthemed)
- Split slides 3+8 (FINDING 2 — densest overflow) - Split slides 3+8 (FINDING 2 — densest overflow)
---
## v1.23 RESEARCH — Nova Deck Cleanup & Python PPTX
> Phase: 0 RESEARCH. Milestone v1.23 (Nova Deck Cleanup & Python PPTX).
> docs/render/test NFR milestone. Surface: deck markdown, Marp inline
> style CSS, Python render scripts, tests, CI YAML, README.
> Researcher: ci-researcher. Sources: ACDL codebase (v1.22 tree) +
> the deleted clean reference HTML at /root/gl/acdl/docs/presentations/
> the-developer-experience.html + python-pptx docs + Marp rendered HTML
> inspection.
### FINDING 1 — Marp `default` theme + inline `style:` block (the clean reference)
The deleted clean reference deck
`/root/gl/acdl/docs/presentations/the-developer-experience.html`
(commit eb43e08) used `theme: default` + an inline `style:` block in the
Marp frontmatter. The exact inline CSS (extracted from the `data-style`
attribute repeated on every `<section>` in the rendered HTML) is:
```css
section {
font-family: "Akkurat Pro", "Helvetica Neue", "Arial", sans-serif;
font-size: 26px;
color: #1B1B1B;
}
h1 { color: #D6002A; font-size: 40px; margin-bottom: 0.3em; }
h2 { color: #D6002A; font-size: 32px; margin-bottom: 0.2em; }
section.title { background: #1B1B1B; color: #fff; border-top: 8px solid #D6002A; }
section.title h1 { color: #fff; }
table { font-size: 22px; width: 100%; }
th { background: #F0F0F0; }
blockquote { border-left: 4px solid #D6002A; color: #2E2E2E; font-size: 24px; }
pre { font-size: 16px; line-height: 1.3; }
code { font-size: 16px; }
img { display: block; margin: 0 auto; max-height: 280px; }
.badge {
display: inline-block; padding: 2px 8px; border-radius: 4px;
font-size: 16px; font-weight: 600;
}
.planned { background: #fef3c7; color: #78350f; }
```
**What Marp's built-in `default` theme provides for free** (from the
embedded `@theme default` CSS in the reference HTML):
- `section { width:1280px; height:720px; box-sizing:border-box; overflow:hidden; position:relative }`
— slide dimensions + box model.
- Base section typography: `font-family: var(--fontStack-sansSerif)`
(system sans), `font-size: 16px`, `line-height: 1.5`, `color:
var(--fgColor-default)`, `background: var(--bgColor-default)` (white).
**Note: default does NOT set padding** — wait, correction: the
`default` theme's embedded CSS in this Marp version does NOT include
an explicit `padding` on `section`; the nova-sp-theme.css comment
claiming `padding: 56px 64px` was for a different Marp version. The
inline `style:` block above does not set padding either, implying the
default theme's inheriting UA stylesheet padding or Marp's container
provides it. **This is the key thing to verify in P2:** whether
`theme: default` + the inline `style:` block produces padding that
reserves header/footer chrome space. The v1.22 root-cause was ZERO
padding with the standalone theme; the inline `style:` block above
also does not set padding, so the clean look may have relied on the
default theme's own padding (which Marp's default theme CSS
*does* set: `section{padding:56px 64px}` is in the default theme's
canonical CSS even if not visible in this rendered HTML's inlined
subset — the rendered HTML strips the theme CSS into a `<style>` block
at the top that we did not fully inspect). **Recommendation:** the
inline `style:` block for v1.23 SHOULD explicitly set section padding
(`padding: 48px 56px 40px` as v1.22 settled on) to be safe, OR verify
the default theme provides it. The reference deck's clean look
suggests the default theme's padding was adequate.
- Pagination: `section::after { content: attr(data-marpit-pagination);
bottom:0; right:0; padding:inherit }` — page numbers via CSS pseudo-
element on `section[data-marpit-pagination]`. Free with `paginate:
true`.
- Header/footer chrome: `default` theme renders `header`/`footer`
elements from frontmatter `header:`/`footer:` directives. The inline
`style:` block does NOT suppress them on title slides (the reference
deck's title slide has `<header>The Developer Experience</header>` +
`<footer>Internal</footer>` visible). v1.22's `section.title header,
section.title footer { display: none }` rule should be ported into the
v1.23 inline `style:` block to keep title slides clean.
- H1/H2 base sizing: `:is(h1, marp-h1){font-size:2em;margin-block:0.67em}`
— the inline `style:` block overrides these with explicit px sizes.
- Font-family: the default theme uses GitHub's `--fontStack-sansSerif`
(system fonts); the inline `style:` overrides to Akkurat Pro stack.
**What the inline `style:` block must override** (not inherited from
default): font-family (Akkurat Pro stack), font-size (26px vs 16px),
font-color (#1B1B1B), H1/H2 colors (#D6002A), title-slide background
(#1B1B1B + 8px red top border), table styling, blockquote styling,
image max-height. **What it inherits for free:** slide dimensions
(1280×720), pagination, header/footer rendering, base box model.
**Confidence: 0.95** (the CSS is read verbatim from the rendered
`data-style` attribute on every `<section>`; the default theme
inheritance is confirmed by the `data-theme="default"` attribute on
every section).
### FINDING 2 — Marp HTML-in-markdown passthrough
Marp (via Marpit, the underlying markdown-it-based engine) **passes
through raw HTML blocks** in markdown slides. Confirmed by the
reference deck:
`/root/gl/acdl/docs/presentations/the-developer-experience-marp.md`
contains inline HTML like
`<span class="badge planned">Planned</span>` inside slide bodies, and
the rendered HTML (`the-developer-experience.html`) contains the
verbatim `<span class="badge planned">Planned</span>` inside the
`<section>` elements. The `.badge` and `.planned` classes are styled
by the inline `style:` block (Finding 1).
**Gotchas confirmed:**
- HTML blocks render correctly inside `## Slide N — Title` H2 slides
(the reference deck's slide 5 has `<span class="badge planned">` in a
table cell; slide 7 has it in a bullet list item).
- HTML does NOT interfere with pagination (the `data-marpit-pagination`
attribute is present on every paginated section regardless of HTML
content).
- HTML inline elements (`<span>`, `<div>`, `<strong>`) render; block
elements (`<div class="benefit">text</div>`) also render — Marpit's
markdown-it `html: true` option is on by default.
- **Caveat:** Marp does NOT sanitize HTML by default (no DOMPurify);
raw `<script>` would pass through too. For our use case (styling
divs/spans) this is fine.
- The current nova deck markdown does NOT use any `<div>` HTML blocks
(`grep -c "<div" nova-autonomous-cloud-delivery-marp.md` = 0 in the
slide body; only the bespoke-marp-osc div in the rendered HTML
chrome). So the v1.23 plan to add `.benefit` class divs is a NEW
pattern — it will work, but is untested in this repo's deck.
**Confidence: 0.95** (confirmed by direct inspection of the reference
deck's source + rendered HTML; Marpit's html passthrough is documented
behavior).
### FINDING 3 — python-pptx capabilities for the structured PPTX
python-pptx (the `python-pptx` PyPI package) supports all capabilities
required for `scripts/render_pptx.py`. API calls confirmed against the
python-pptx documentation (v1.0.2 latest; v0.6.23 is the established
minimum):
(a) **16:9 slide dimensions:**
```python
from pptx import Presentation
from pptx.util import Inches, Pt
prs = Presentation()
prs.slide_width = Inches(13.333) # 16:9 widescreen
prs.slide_height = Inches(7.5)
```
(b) **Slide background color (black for title slide):**
```python
from pptx.dml.color import RGBColor
slide = prs.slides.add_slide(prs.slide_layouts[6]) # blank layout
bg = slide.background
bg.fill.solid()
bg.fill.fore_color.rgb = RGBColor(0x1B, 0x1B, 0x1B) # sp-black
```
(Note: `slide.background` is available since python-pptx 0.6.19+.)
(c) **Colored rectangle as top border bar:**
```python
from pptx.enum.shapes import MSO_SHAPE
from pptx.util import Inches
shape = slide.shapes.add_shape(
MSO_SHAPE.RECTANGLE,
Inches(0), Inches(0), # left, top
Inches(13.333), Inches(0.08) # width, height (8px ≈ 0.08in at 96dpi)
)
shape.fill.solid()
shape.fill.fore_color.rgb = RGBColor(0xD6, 0x00, 0x2A) # sp-red
shape.line.fill.background() # no border on the rectangle
```
(d) **Text frames with font family/color/size (Akkurat Pro → Helvetica
Neue → Arial fallback):**
```python
from pptx.util import Pt
txBox = slide.shapes.add_textbox(Inches(0.5), Inches(0.5), Inches(12), Inches(1))
tf = txBox.text_frame
p = tf.paragraphs[0]
run = p.add_run()
run.text = "Slide title"
run.font.name = "Akkurat Pro"
run.font.size = Pt(40)
run.font.bold = True
run.font.color.rgb = RGBColor(0xD6, 0x00, 0x2A)
# Font fallback: python-pptx sets a single font.name; PowerPoint's own
# font substitution handles the fallback chain. To set a fallback
# explicitly, set the font's `<a:latin>` + `<a:ea>` + `<a:cs>` typeface
# attributes, OR rely on PowerPoint rendering "Akkurat Pro" → missing →
# substitutes Helvetica Neue / Arial per the OS font substitution table.
# The simplest approach: set name="Akkurat Pro"; PowerPoint does the
# fallback. For an explicit fallback, manipulate the XML:
from pptx.oxml.ns import qn
rPr = run._r.get_or_add_rPr()
latin = rPr.find(qn('a:latin'))
if latin is None:
latin = rPr.makeelement(qn('a:latin'), {})
rPr.append(latin)
latin.set('typeface', 'Akkurat Pro')
# (PowerPoint reads only the first typeface; fallback is OS-level.)
```
**Key constraint:** python-pptx does NOT support a CSS-style font
fallback stack. Setting `run.font.name` sets one typeface; PowerPoint's
font substitution handles the fallback when the font is missing. This
is adequate for our use case (Akkurat Pro is the named font; on
systems without it, PowerPoint substitutes Helvetica/Arial).
(e) **Bullet lists:**
```python
tf = txBox.text_frame
for i, item in enumerate(bullets):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
p.text = item
p.level = 0 # bullet level (0 = top)
# Bullet character: python-pptx does not expose a high-level bullet
# API; the default paragraph style from the layout provides bullets.
# For explicit bullet control, manipulate the XML <a:buChar> or
# set p.font + use the layout's list style.
```
**Constraint:** python-pptx has limited high-level bullet support; the
`paragraph` level controls indentation but the bullet glyph comes from
the layout's list style. For a blank layout (layout 6), paragraphs
have NO bullet by default — you must add `<a:buChar>` to the paragraph
properties XML, or use a layout that has bullets. **Recommendation:**
use `slide_layouts[1]` (Title and Content) for bullet slides, which
provides bullets natively; or inject `<a:buChar char="•"/>` via XML.
(f) **Native tables (`add_table`):**
```python
from pptx.util import Inches
rows, cols = len(data), len(headers)
table = slide.shapes.add_table(rows, cols, Inches(0.5), Inches(1.5),
Inches(12), Inches(4)).table
# Headers
for c, h in enumerate(headers):
table.cell(0, c).text = h
# style header cell
cell = table.cell(0, c)
cell.fill.solid()
cell.fill.fore_color.rgb = RGBColor(0xF0, 0xF0, 0xF0)
# Body
for r, row in enumerate(data, start=1):
for c, val in enumerate(row):
table.cell(r, c).text = str(val)
```
**Confirmed:** `add_table` is fully supported since 0.6.19; returns a
`Table` object with `.cell(r,c).text` + `.fill`.
(g) **Embedding images (`add_picture`, auto-embeds in ppt/media/):**
```python
slide.shapes.add_picture(
"docs/presentations/assets/png/platform-pipeline.png",
Inches(1), Inches(1),
width=Inches(11), height=Inches(5)
)
```
**Confirmed:** python-pptx auto-embeds the image binary into the
`ppt/media/` directory inside the .pptx zip; the slide XML references
it via a relationship ID. This is native — no manual zip manipulation
needed. The image is copied once; multiple slides referencing the
same image share the media file.
(h) **Italic text:**
```python
run.font.italic = True # supported since 0.6.0
```
**Version constraint:** `python-pptx>=0.6.23` is the right minimum
(stable release with `slide.background`, `add_table`, `add_picture`
all mature). The latest is 1.0.2 (2024); 0.6.23 (2021) is safe. The
`slide.background` API landed in 0.6.19; `add_table` in 0.6.19;
`add_picture` since 0.6.0. **Recommendation:** pin
`python-pptx>=0.6.23` in `pyproject.toml` `[project.optional-
dependencies] test`.
**Confidence: 0.90** (API calls confirmed against python-pptx docs;
the font fallback + bullet limitations are the residual uncertainty —
both have XML workarounds).
### FINDING 4 — Image inlining (stdlib-only `base64` + `re` + `mimetypes`)
The current rendered HTML
(`docs/presentations/nova-autonomous-cloud-delivery.html`) embeds
images as **plain `<img src="assets/png/...">` tags with relative
paths** — NOT base64-inlined. Confirmed by grep: `<img
src="assets/png/platform-pipeline.png" ...>` (slide 7), `<img
src="assets/png/telemetry-live-ops.png" ...>` (slide 11). They are
NOT SVG `xlink:href`; they are standard `<img>` elements inside the
SVG `<foreignObject>` wrapper that Marp uses for slide rendering.
**The stdlib-only approach is sufficient:**
```python
import base64, re, mimetypes
html = Path("...html").read_text()
def inline(match):
src = match.group(1)
if src.startswith("data:"): # already inlined
return match.group(0)
mime = mimetypes.guess_type(src)[0] or "image/png"
data = base64.b64encode(Path(src).read_bytes()).decode()
return f'<img src="data:{mime};base64,{data}"'
# Match <img src="..."> with single or double quotes
html = re.sub(r'<img\s+src="([^"]+)"', inline_img, html)
html = re.sub(r"<img\s+src='([^']+)'", inline_img, html)
```
**Edge cases confirmed:**
- Marp's SVG-foreignObject wrapper: the `<img>` tags are INSIDE the
`<foreignObject><section>...</section></foreignObject>` — the regex
still matches them (they're standard HTML img tags in the HTML
namespace). No special handling needed.
- Quoted vs unquoted src: Marp always emits double-quoted `src="..."`;
handle both for safety.
- Relative vs absolute paths: Marp emits relative paths
(`assets/png/X.png`); the script must resolve them relative to the
HTML file's directory. `Path(html_path).parent / src`.
- Already-inlined images (`data:` URIs): skip (the regex checks for
`src.startswith("data:")`).
- Marp does NOT use `xlink:href` for images in HTML output (that's
only for SVG-only output mode); the HTML output uses standard
`<img src>`. Confirmed by grep: 0 occurrences of `xlink:href` in
the current HTML.
- The `--allow-local-files` flag in `render_slides.sh` is what makes
Marp keep the local relative paths (without it, Marp would skip
local images in the standalone HTML).
**No external library needed.** stdlib `base64` + `re` + `mimetypes`
+ `pathlib` is sufficient.
**Confidence: 0.95** (verified by direct inspection of the current
HTML; the regex handles all observed patterns).
### FINDING 5 — Tests that need updating (`tests/test_slides_pipeline.py`)
Full read of `tests/test_slides_pipeline.py` (373 lines). Tests that
assert on v1.23-affected surfaces:
**(a) `theme: nova-sp` / `nova-sp-theme.css` via `--theme`:**
- **Line 55-64 `test_marp_deck_uses_sp_theme`**: asserts `"nova-sp" in
frontmatter`. After v1.23: frontmatter should have `theme: default`;
this test must assert `"theme: default"` instead (or assert the inline
`style:` block is present).
- **Line 66-74 `test_marp_deck_not_using_default_theme`**: asserts
`"theme: default" not in frontmatter`. After v1.23: this test must
be DELETED or INVERTED (we're reverting TO `theme: default`).
- **Line 36-38 `test_sp_theme_css_exists`**: asserts
`nova-sp-theme.css` is a file. After v1.23: keep (the CSS is retired
from render but kept as a reference file).
- **Line 41-45 `test_sp_theme_css_has_snp_colors`**: asserts the CSS
has S&P colors. After v1.23: keep (reference file still has colors).
- **Line 275-282 `test_theme_css_has_section_padding`**: asserts the
CSS `section` rule has padding. After v1.23: this test targets the
RETIRED CSS — it should be retargeted to the inline `style:` block in
the marp deck frontmatter (assert the inline style has section
padding).
- **Line 284-293 `test_theme_css_suppresses_title_chrome`**: asserts
`section.title header` + `display: none` in the CSS. After v1.23:
retarget to the inline `style:` block.
- **Line 295-302 `test_theme_css_has_aspect_ratio_aware_images`**:
asserts `object-fit` + `max-width` in the CSS. After v1.23: retarget
to the inline `style:` block (or drop if the inline style uses the
simpler `max-height: 280px` from the reference deck).
- **Line 353-359 `test_html_embeds_theme`**: asserts `"--sp-red"` +
`"padding:"` in the committed HTML. After v1.23: the inline `style:`
block uses literal `#D6002A` (not the `--sp-red` CSS variable — the
reference deck's inline style did NOT use CSS variables). This test
must change to assert `"#D6002A"` + `"padding:"` (or `"font-family:
\"Akkurat Pro\""`).
**(b) Both `.md` and `-marp.md` existing:**
- **Line 31 `SOURCE_MD` constant** + **Line 240-263
`test_source_md_no_internal_citations_in_slides`** +
**Line 266-270 `test_source_md_no_badges`**: assert the plain `.md`
is the source of truth. After v1.23: the plain `.md` is DELETED
(consolidated into `-marp.md`); these tests must be DELETED or
retargeted to the consolidated `-marp.md`.
**(c) `marp-cli` invocation specifics:**
- **Line 83-90 `test_render_slides_script_renders_mermaid`**: asserts
`mermaid-cli` + `sp-theme.json` in the script. After v1.23: keep
(mermaid render unchanged).
- **Line 92-98 `test_render_slides_script_renders_marp`**: asserts
`marp-cli` + `.html` + `.pptx` in the script. After v1.23: the script
no longer passes `--theme nova-sp-theme.css`; it may still produce
`.pptx` via marp-cli OR via the new `render_pptx.py`. This test must
be updated to reflect the new invocation (marp-cli for HTML only;
render_pptx.py for the structured PPTX).
- **Line 327-332 `test_render_slides_has_2x_scale`**: asserts `-s 2`
+ `-b transparent`. After v1.23: keep (mermaid flags unchanged).
- **Line 335-344 `test_render_slides_pins_cli_versions`**: asserts
`marp-cli@` + `mermaid-cli@` pinned, no `@latest`. After v1.23: keep
for mermaid; if marp-cli is still used for HTML, keep for marp too.
- **Line 346-351 `test_render_deck_removed`**: asserts
`render_deck.sh` is deleted. After v1.23: keep.
**(d) Slide count:**
- **Line 204-218 `test_marp_deck_slide_count`**: asserts 20 main + 1
appendix = 21 slides. After v1.23: the word-count trim may change
the count; update to match the final count (likely stays 20+1 or
drops to fewer if slides merge).
- **Line 361-373 `test_html_slide_count_matches_marp`**: asserts the
HTML `<section>` count matches the deck count. After v1.23: keep
(recompute expected from the final deck).
**(e) Other affected tests:**
- **Line 137-143 `test_readme_no_retired_decks`**: asserts README
doesn't list old decks. After v1.23: keep.
- **Line 146-150 `test_readme_no_old_deck_name`**: asserts README
references `nova-autonomous-cloud-delivery`. After v1.23: keep.
- **Line 153-156 `test_old_deck_files_removed`**: asserts old
`nova-no-humans-platform*` files are gone. After v1.23: keep.
- **Line 159-162 `test_marp_deck_no_badges`**: asserts no `badge` in
marp deck. After v1.23: keep.
- **Line 165-177 `test_marp_deck_no_version_in_footer`**: asserts no
version in frontmatter. After v1.23: keep.
- **Line 179-190 `test_marp_deck_title_slide_no_version_subtitle`**:
asserts no `v1.18` subtitle. After v1.23: keep.
- **Line 193-202 `test_marp_deck_title_is_autonomous_cloud_delivery`**:
asserts the title. After v1.23: keep (unless "penetrate" removal
touches the title — it doesn't).
- **Line 221-237 `test_marp_deck_no_internal_citations`**: asserts no
D-###/REQ-###/.py paths. After v1.23: keep.
- **Line 304-324 `test_png_aspect_ratios_sane`**: asserts PNG aspect
ratios in [0.4, 4.0]. After v1.23: keep.
- **Line 107-119 `test_slides_ci_workflow_*`**: asserts slides.yml
exists + triggers on presentations. After v1.23: keep (update if the
workflow changes).
**Summary:** ~12 tests need updating; the key inversions are
`test_marp_deck_not_using_default_theme` (delete/invert) and the
`test_theme_css_*` trio (retarget from the standalone CSS file to the
inline `style:` block). The `test_source_md_*` tests must be deleted
(plain `.md` is gone).
**Confidence: 0.95** (direct line-by-line read of the test file).
### FINDING 6 — `scripts/attach_release_asset.py`
Read in full (80 lines). Current behavior:
- CLI: `python3 scripts/attach_release_asset.py <file-path> <release-id>`
- Resolves `NOVA_GITEA_TOKEN` (or `ACDL_GITEA_TOKEN` fallback) from
`.env.secrets`/`.env`.
- POSTs to `/api/v1/repos/{owner}/{repo}/releases/{id}/assets` with
multipart form: `name=<filename>` + `attachment=<file bytes>`.
- Single-file upload per invocation.
**Change needed for v1.23:** attach BOTH `.pptx` (Marp PPTX) AND
`-python.pptx` (python-pptx structured PPTX). Two options:
1. Call the script twice (once per file) from the ship workflow.
2. Extend the script to accept multiple file paths: `attach_release_asset.py
<file1> <file2> ... <release-id>` (loop over files, same release-id).
**Recommendation:** option 2 (extend to multiple files) — minimal,
backward-compatible (if only 1 file given, same behavior). The
release-id stays the last positional arg; all preceding args are file
paths.
**Confidence: 0.95** (direct read; the change is mechanical).
### FINDING 7 — `workflows-src/slides.yml`
Read in full (31 lines). Current CI steps:
1. `actions/checkout@v4` (fetch-depth 0).
2. `actions/setup-node@v4` (node 20).
3. "Install Chrome" — runs `npx --yes @marp-team/marp-cli@latest
--version` + `npx --yes @mermaid-js/mermaid-cli --version` (NOTE:
uses `@latest` — NOT pinned, contradicts REQ-257's pinning rule for
`render_slides.sh`; the workflow is separate from the script).
4. "Render slides" — `bash scripts/render_slides.sh`.
5. "Commit rendered artifacts" — `git add docs/presentations/*.html
docs/presentations/*.pptx docs/presentations/assets/png/*.png` +
commit + push.
**Where `python-pptx` install would go:** a new step between "Install
Chrome" and "Render slides":
```yaml
- uses: actions/setup-python@v5
with: { python-version: '3.11' }
- name: Install python-pptx
run: pip install python-pptx>=0.6.23
```
Then `render_slides.sh` (or a new `render_pptx.py` invocation) uses
it. OR: `pip install -e ".[test]"` if python-pptx is added to
`pyproject.toml [project.optional-dependencies] test`.
**What the commit list should include after v1.23:** the
`git add` line must include:
- `docs/presentations/*.html` (re-rendered HTML, with inlined images)
- `docs/presentations/*.pptx` (Marp PPTX — kept as the "fallback" PPTX)
- `docs/presentations/*-python.pptx` (NEW — the python-pptx structured
PPTX)
- `docs/presentations/assets/png/*.png` (re-rendered mermaid PNGs)
**Also:** the workflow uses `@latest` for both CLIs in the "Install
Chrome" step — this should be pinned to match `render_slides.sh`'s
pinned versions (`@marp-team/marp-cli@4.5.0`,
`@mermaid-js/mermaid-cli@11.16.0`) for reproducibility.
**Confidence: 0.95** (direct read).
### FINDING 8 — `docs/presentations/README.md` current structure
Read in full (249 lines). Current 4-step process structure:
- **Step 1 — Full markdown (source of truth):** `<deck-name>.md` with
`## Slide N — Title` + `> **Speaker notes:**` blocks + mermaid code
blocks.
- **Step 2 — Marp deck synthesis:** `<deck-name>-marp.md` with Marp
frontmatter (`theme: nova-sp`), no speaker notes, embedded PNGs, no
badges.
- **Step 3 — Render to HTML and PPTX:** `bash scripts/render_slides.sh
<deck-name>`; HTML committed (with base64 images — NOTE: this is
aspirational; the current HTML does NOT inline images, which is what
`inline_images.py` will fix); PPTX committed + release-attached.
- **Step 4 — Talking points:** `<deck-name>-talking-points.md` with
one section per Marp slide, 3-6 bullets + key takeaway.
**What to rewrite for v1.23:**
- The 4-step process becomes a **3-step process** (Step 1 plain `.md`
is DELETED → consolidated into `-marp.md`; the source of truth IS the
`-marp.md` with speaker notes in HTML comments, OR speaker notes are
dropped entirely). OR: keep 4 steps but Step 1's output merges into
Step 2 (the `-marp.md` becomes the source of truth, with speaker
notes preserved as HTML comments that Marp strips from rendering).
- Step 2 frontmatter: `theme: default` + inline `style:` block (not
`theme: nova-sp`).
- Step 3: HTML rendered by Marp (`render_slides.sh`), images inlined
by `scripts/inline_images.py` (NEW), PPTX rendered by BOTH marp-cli
(fallback `.pptx`) AND `scripts/render_pptx.py` (structured
`-python.pptx`).
- The directory layout block (line 108-123) must drop the plain `.md`
row + add `inline_images.py` + `render_pptx.py` rows + the
`-python.pptx` row + note `nova-sp-theme.css` is retired (reference
only).
- The "Current decks" table (line 237-239) must list both PPTX files.
- The slide-count convention (line 128-136) must reflect the final
v1.23 count (after word-count trim + "penetrate" removal).
- The "Adding a new presentation" section (line 209-233) must be
rewritten for the single-document + dual-PPTX pipeline.
**Confidence: 0.95** (direct read).
### FINDING 9 — Persona assessment for v1.23
The v1.23 scope: deck markdown editing (consolidate `.md` + `-marp.md`,
trim word count, remove "penetrate"), Marp inline `style:` CSS, Python
scripts (`inline_images.py` + `render_pptx.py`), tests, CI YAML
(`slides.yml`), README. The v1.22 roster had lead-developer +
backend-engineer active; frontend + data deactivated.
**Assessment:** the same roster applies, with one territory shift.
- **lead-developer** (active): owns the deck markdown (consolidation,
word-count trim, "penetrate" removal), the inline `style:` CSS block
(porting the reference deck's CSS), the README rewrite, and all
`.ciagent/**` metadata. The inline CSS is a Marp stylesheet block, not
a React/Next.js system — stays lead-developer (D-148 precedent from
v1.22).
- **backend-engineer** (active): owns the Python scripts
(`inline_images.py`, `render_pptx.py` — python-pptx is a backend
library), the `render_slides.sh` updates, the tests, the CI YAML,
and the `attach_release_asset.py` extension. This is a shift from
v1.22 (where backend owned bash + pytest); v1.23 adds Python to
backend's territory.
- **frontend-engineer** (deactivated): no React/Next.js. The inline
CSS is a Marp stylesheet, not frontend. Stays deactivated (D-148
precedent).
- **data-engineer** (deactivated): no schema/DB/ORM. Stays deactivated.
**Custom "docs/deck" persona?** Not warranted. The work splits cleanly
into lead-developer (narrative + CSS + markdown + README + metadata)
and backend-engineer (Python + bash + tests + CI). A "docs-engineer"
or "slides-engineer" persona would fragment ownership of the deck
markdown (lead) and the render scripts (backend). D-149 precedent from
v1.22 applies: no new personas.
**One nuance:** python-pptx is a new dependency (backend territory) but
the PPTX CONTENT (slide layout, text, image placement) is a design
decision that overlaps with lead-developer's deck ownership. The
`render_pptx.py` script is backend (Python code); the slide-by-slide
layout decisions within it (which image goes where, font sizes) are
co-owned with lead-developer. Territory enforcement = `warn` (cross-
territory edits logged, not blocked).
**Confidence: 0.90** (the roster follows v1.22 precedent; the only new
element is python-pptx, which is unambiguously backend).
### FINDING 10 — Risks / pitfalls (5)
1. **Marp `default` theme padding conflict.** The reference deck's
inline `style:` block does NOT set `section` padding. The v1.22
root-cause was ZERO padding with the standalone `nova-sp-theme.css`.
If `theme: default`'s built-in padding differs from v1.22's settled
`48px 56px 40px`, slides may re-overflow. **Mitigation:** explicitly
set `section { padding: 48px 56px 40px }` in the v1.23 inline
`style:` block (do not rely on the default theme's padding). Verify
by re-rendering + visual inspection in P2.
2. **python-pptx font fallback when Akkurat Pro isn't installed.**
python-pptx sets a single `run.font.name = "Akkurat Pro"`; on
systems without Akkurat Pro (CI, most laptops), PowerPoint
substitutes a default font (often Calibri), NOT Helvetica Neue /
Arial. The CSS stack `Akkurat Pro → Helvetica Neue → Arial` does
NOT translate to python-pptx (no CSS-style fallback). **Mitigation:**
accept PowerPoint's OS-level substitution (the deck will render in
a sans font either way); OR set `run.font.name = "Arial"` directly
for the structured PPTX (guaranteed present on all systems) and
document that the Marp HTML deck uses the full Akkurat Pro stack
while the python PPTX uses Arial. The dual-PPTX approach lets the
Marp PPTX (rendered by Chromium with the CSS stack) carry the
brand font, while the python PPTX (structured, editable) carries
Arial for editability.
3. **Image inlining breaking if Marp embeds images as SVG
`xlink:href`.** The current HTML uses `<img src="assets/png/...">`
(confirmed by grep). BUT if a future Marp version switches to SVG
`xlink:href` for the HTML output, the `inline_images.py` regex
would miss them. **Mitigation:** the regex should ALSO match
`xlink:href="assets/..."` patterns as a fallback; OR pin the
Marp version (already pinned at `@4.5.0` in `render_slides.sh`) so
the output format is stable.
4. **Consolidated doc losing speaker notes if HTML comments are
stripped by Marp.** The plan consolidates the plain `.md` (with
`> **Speaker notes:**` blockquotes) into the `-marp.md`. Marp strips
HTML comments (`<!-- ... -->`) from rendered output but preserves
blockquotes (`> ...`) as `<blockquote>` elements. If speaker notes
are moved to HTML comments to hide them from the audience, Marp
strips them — and they're lost from the rendered HTML. If they stay
as blockquotes, they're visible in the audience deck (bad).
**Mitigation:** either (a) drop speaker notes entirely from the
consolidated `-marp.md` (the talking-points file is the presenter
cue sheet; notes aren't needed in the deck), or (b) keep them as
HTML comments (Marp strips from render, but they persist in the
source markdown for git-diff readability). Option (b) is safer —
the source markdown keeps the notes; the rendered HTML + PPTX
don't. Verify Marp strips `<!-- -->` from HTML output (confirmed:
Marp's markdown-it does strip HTML comments by default).
5. **`test_html_embeds_theme` asserts `--sp-red` CSS variable, but the
reference inline `style:` uses literal `#D6002A`.** The v1.22 test
(line 353-359) asserts `"--sp-red"` in the committed HTML. The
reference deck's inline `style:` block uses literal `#D6002A`, not
a CSS variable. If v1.23 ports the reference style verbatim, the
test fails. **Mitigation:** update the test to assert `"#D6002A"`
(literal) OR use CSS variables in the inline `style:` block (Marp
supports `:root { --sp-red: #D6002A }` + `color: var(--sp-red)` in
the inline style — but the reference deck didn't, so matching the
reference means literal colors). Recommendation: use literal colors
in the inline `style:` block (matches the reference deck's clean
approach) + update the test.
**Confidence: 0.90** (pitfalls identified from the research findings
above; mitigations are concrete).
---
## Assumptions logged (v1.23)
| ID | Assumption | Confidence | Rationale |
|----|------------|------------|-----------|
| A-1.23.1 | The consolidated `-marp.md` will keep speaker notes as HTML comments (`<!-- -->`), which Marp strips from rendered HTML/PPTX but preserves in the source markdown for git readability. | 0.80 | Marp's markdown-it strips HTML comments by default; the source markdown keeps them. The alternative (drop notes entirely) loses the presenter context. |
| A-1.23.2 | `python-pptx` will be added to `pyproject.toml [project.optional-dependencies] test` (not the runtime `dependencies`), since the structured PPTX is a build/test artifact, not a runtime dependency. | 0.90 | The render scripts run in CI + locally, not in the platform runtime. Matches the `moto`/`pytest` pattern. |
| A-1.23.3 | The Marp PPTX (`.pptx`) is kept as a "fallback" alongside the new python-pptx structured PPTX (`-python.pptx`); both are committed + release-attached. | 0.85 | The plan says "dual PPTX"; the Marp PPTX is the quick render, the python PPTX is the editable one. Keeping both gives consumers a choice. |
| A-1.23.4 | The `nova-sp-theme.css` file is KEPT in `docs/presentations/assets/` as a reference (retired from the render pipeline) but NOT deleted. | 0.90 | The plan says "keep as a reference (retired from render)"; deletion would lose the v1.22 work history. |
| A-1.23.5 | The slide count after the word-count trim + "penetrate" removal will stay at 20 main + 1 appendix (the trim is within-slide prose, not slide removal). | 0.75 | The plan says "trim word count" (prose), not "remove slides"; but if a slide becomes too thin it may merge. To be confirmed in execution. |
---
## Decisions surfaced (v1.23 research)
These research-grounded decisions will be bound in the CLARIFY stage:
- **D-XXX (0.90):** Revert to `theme: default` + inline `style:` block
(port the reference deck's exact CSS from Finding 1). Keep
`nova-sp-theme.css` as a retired reference file. Confidence 0.90 —
the reference deck's clean look is the target; the CSS is read
verbatim.
- **D-XXX (0.85):** Build `scripts/inline_images.py` (stdlib-only:
`base64` + `re` + `mimetypes`) to inline `<img src="assets/...">` →
`data:` URIs in the rendered HTML. No external library. Confidence
0.95 — confirmed the current HTML uses plain `<img src>`.
- **D-XXX (0.85):** Build `scripts/render_pptx.py` using python-pptx
(>=0.6.23) for a structured, editable PPTX (`-python.pptx`). Keep
the Marp PPTX (`.pptx`) as a fallback. Both committed + release-
attached. Confidence 0.90 — python-pptx API confirmed.
- **D-XXX (0.80):** Consolidate the plain `.md` + `-marp.md` into a
single `-marp.md`. Speaker notes move to HTML comments (stripped by
Marp from render, preserved in source). The plain `.md` is deleted.
Confidence 0.80 — the HTML-comment approach preserves notes for git
without leaking them to the audience.
- **D-XXX (0.85):** No new personas for v1.23. lead-developer (deck
markdown + inline CSS + README + metadata) + backend-engineer
(Python scripts + bash + tests + CI). Territory enforcement = `warn`
for `render_pptx.py` (script is backend; slide layout decisions are
co-owned with lead-developer). Confidence 0.90 — follows v1.22 D-
148/D-149 precedent.