Make PDF a required parallel output, generated via headless Chromium

Bryan: PDF needs to happen automatically, not be a manual export --
not everyone can open the HTML comfortably. This was designed back in
CW-027's Delivery Architecture discussion but never actually built into
this skill; step 7 only produced HTML.

Fixed: step 7 now generates the PDF immediately after the HTML, via
Playwright + headless Chromium (confirmed available at /opt/pw-browsers),
using page.emulate_media('print') so the PDF gets exactly the same print
CSS this skill already ships -- no separate print logic to maintain, no
relying on someone hitting Ctrl+P themselves.

Verified end-to-end before writing this into the spec: patched a real
brief with the current print CSS, rendered to PDF, confirmed correct
Letter pagination (5 pages, no blank-page artifacts) and correct
rendering of the modern CSS (color-mix, gradients, custom properties)
that simpler non-browser PDF converters often get wrong.

Step 8 (archive) updated: both HTML and PDF save to Meeting Briefs/
every time, not PDF-on-request.
This commit is contained in:
Bryan Gilliom (via Claude) committed 2026-09-22 21:36:58 +00:00
1 parent 2209a19eb7
commit 93c99c04d2
1 file changed
+54 -8
+54 -8
View File
@@ -53,6 +53,10 @@ Other tools this skill uses:
- **Google Workspace** (MCP) — Gmail, Google Chat, Drive, Sheets
- **Google Drive** — the topic's continuity folder (`_topic-state.md` + brief history
+ `Meeting Briefs/` + `Meeting Transcripts/`)
- **Playwright (Python, headless Chromium)** — for PDF generation from the rendered
HTML (step 7). Confirmed available in this environment at `/opt/pw-browsers`; if a
future environment lacks it, that's a real gap to flag, not something to silently
skip — HTML-only output is a regression from what this skill is supposed to do.
- **MPM Technical Reference System** — a Drive-based product/technical knowledge
registry (131 entity files as of 2026-09-22: brand voice, platform/software,
display technologies, products, engineering, concepts, proof/evidence). Read
@@ -184,15 +188,51 @@ sections only where the meeting's actual content has a structure that calls for
Real, verified content over generic summary — carry forward specific facts (names,
dates, exact technical decisions), not vague paraphrase.
### 7. Render
Single self-contained HTML file. Include `assets/brief-components.css` inline (don't
link out to it — the brief must survive as a standalone file). Include: sticky jump-nav,
a working light/dark toggle (`data-theme` attribute, default light), and the
### 7. Render — HTML and PDF, always both, not HTML alone
**PDF is a required parallel output, not an optional extra someone has to remember to
export.** Not everyone can open an HTML file comfortably (attachments, security
posture, unfamiliarity) — the PDF is what makes a brief actually shareable outside a
browser.
**HTML first:**
Single self-contained file. Include `assets/brief-components.css` inline (don't link
out to it — the brief must survive as a standalone file). Include: sticky jump-nav, a
working light/dark toggle (`data-theme` attribute, default light), and the
`@media print` rules from the shared CSS (forces light, hides nav/toggle, avoids
awkward page breaks). Small brief-specific CSS additions beyond the shared stylesheet
(e.g. a status-source badge, a role subtitle under an attendee's name) are fine —
append them, don't fork the canonical rules to get the same effect.
**Then PDF, from that same HTML file, via a real browser — not a simplified
HTML-to-PDF library.** This stylesheet uses modern CSS (`color-mix()`, custom
properties, conic-gradient) that non-browser renderers often get wrong. Use headless
Chromium via Playwright, which is confirmed available in this environment
(`/opt/pw-browsers`):
```python
from playwright.sync_api import sync_playwright
import os
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto('file://' + os.path.abspath('<brief>.html'))
page.emulate_media(media='print') # forces the @media print rules
page.pdf(
path='<brief>.pdf',
format='Letter',
print_background=True, # keep header gradient, card fills, etc.
margin={'top':'0.4in','bottom':'0.4in','left':'0.3in','right':'0.3in'}
)
browser.close()
```
`emulate_media(media='print')` is what makes this reliable — it forces the exact CSS
this skill already ships for print (light theme, hidden nav/toggle, row-level
break-avoid), so the PDF matches what someone would get from Ctrl+P in a real browser,
without relying on them to do that themselves. Verified 2026-09-22: correct Letter
pagination, no blank-page artifacts, gradient/color-mix rendering intact.
**Footer confidentiality note — standard for this brief type.** internal-meeting-brief
is, by definition, internal content. End the footer with an explicit statement of that
(e.g. "Internal. Do not forward to [named external party]." when the topic has one) —
@@ -200,10 +240,10 @@ validated in the Oshkosh brief, where this genuinely matters: the brief discusse
reseller/customer deal in more candid terms than anyone would want forwarded to that
customer.
### 8. Archive — the brief, the transcript, and the manifest update
- Save the generated HTML into the topic folder's `Meeting Briefs/` subfolder
(`YYYY-MM-DD <Meeting> - <Mode> Brief.html`). If a PDF was also generated, save it
alongside with the matching name.
### 8. Archive — the brief (HTML + PDF), the transcript, and the manifest update
- Save both the HTML and the PDF into the topic folder's `Meeting Briefs/` subfolder,
same base filename (`YYYY-MM-DD <Meeting> - <Mode> Brief.html` /`.pdf`). Both, every
time — not PDF-on-request.
- **Save a plain-text copy of the meeting record into a `Meeting Transcripts/`
subfolder** (`YYYY-MM-DD <source name> - Transcript.txt`) — a standing step, not
something to be asked for. This matters beyond convenience: it makes the topic
@@ -270,6 +310,12 @@ Real, valuable findings, each fixed in this revision:
6. **Meeting transcript wasn't being archived — now a standing step.** The raw
transcript needed to be saved alongside the brief so future runs don't depend on
the source connector still being reachable. Now explicit in step 8.
7. **PDF was manual, bolt-on, and done by hand — now a required parallel output.**
The skill only produced HTML; getting a PDF meant the person exporting it
themselves (and hitting the print-CSS bug in the process — see above). Fixed:
step 7 now generates the PDF automatically via headless Chromium (Playwright),
using the exact same print CSS this skill already ships, immediately after the
HTML — not something anyone has to remember to do.
None of these were failures of judgment in the moment — they're gaps between what
this file said and what turned out to actually be needed, now closed.