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 Workspace** (MCP) — Gmail, Google Chat, Drive, Sheets
- **Google Drive** — the topic's continuity folder (`_topic-state.md` + brief history - **Google Drive** — the topic's continuity folder (`_topic-state.md` + brief history
+ `Meeting Briefs/` + `Meeting Transcripts/`) + `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 - **MPM Technical Reference System** — a Drive-based product/technical knowledge
registry (131 entity files as of 2026-09-22: brand voice, platform/software, registry (131 entity files as of 2026-09-22: brand voice, platform/software,
display technologies, products, engineering, concepts, proof/evidence). Read 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, Real, verified content over generic summary — carry forward specific facts (names,
dates, exact technical decisions), not vague paraphrase. dates, exact technical decisions), not vague paraphrase.
### 7. Render ### 7. Render — HTML and PDF, always both, not HTML alone
Single self-contained HTML file. Include `assets/brief-components.css` inline (don't **PDF is a required parallel output, not an optional extra someone has to remember to
link out to it — the brief must survive as a standalone file). Include: sticky jump-nav, export.** Not everyone can open an HTML file comfortably (attachments, security
a working light/dark toggle (`data-theme` attribute, default light), and the 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 `@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 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 — (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. 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 **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 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) — (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 reseller/customer deal in more candid terms than anyone would want forwarded to that
customer. customer.
### 8. Archive — the brief, the transcript, and the manifest update ### 8. Archive — the brief (HTML + PDF), the transcript, and the manifest update
- Save the generated HTML into the topic folder's `Meeting Briefs/` subfolder - Save both the HTML and the PDF into the topic folder's `Meeting Briefs/` subfolder,
(`YYYY-MM-DD <Meeting> - <Mode> Brief.html`). If a PDF was also generated, save it same base filename (`YYYY-MM-DD <Meeting> - <Mode> Brief.html` /`.pdf`). Both, every
alongside with the matching name. time — not PDF-on-request.
- **Save a plain-text copy of the meeting record into a `Meeting Transcripts/` - **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 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 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 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 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. 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 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. this file said and what turned out to actually be needed, now closed.