feat(exporters): real PDF/PPTX/ZIP via system Chrome + pptxgenjs + zip-lib (lazy-loaded)#7
Closed
feat(exporters): real PDF/PPTX/ZIP via system Chrome + pptxgenjs + zip-lib (lazy-loaded)#7
Conversation
Wires the full prompt → artifact → preview → export loop so the README's
"first demo" actually produces a design when an API key is provided.
- packages/templates: externalize the design-generator system prompt as
SYSTEM_PROMPTS.designGenerator with a sibling design-generator.md for
reviewable diffs. Replaces the inline string previously hard-coded in
packages/core. Prompt embeds the research-backed Claude Design rules
(single artifact, Tailwind CDN, semantic HTML, CSS variable tokens,
WCAG AA, no lorem ipsum).
- packages/core: pull SYSTEM_PROMPTS.designGenerator from templates,
collapse the duplicated artifact-extraction loop into a `collect()`
helper, add generate.test.ts (mocks the providers boundary, asserts
empty-prompt error, artifact extraction shape, system-prompt wiring).
- packages/exporters: real exportHtml() that ensures a doctype, injects
the Tailwind CDN tag if missing, stamps a generator meta/banner, and
pretty-prints. PDF / PPTX / ZIP each throw CodesignError with code
EXPORTER_NOT_READY ("ships in Phase 2") — no silent fallbacks
(PRINCIPLES §10). Top-level exportArtifact() dispatches lazily so
unused formats stay out of the cold-start bundle (PRINCIPLES §1).
- apps/desktop: codesign:export IPC backed by Electron dialog
showSaveDialog, validates payload via CodesignError, propagates
Phase-2 errors loudly. Preload exposes window.codesign.export().
Store gains exportActive(format) + a toast slot. PreviewToolbar
renders an Export dropdown with HTML enabled and PDF/PPTX/ZIP
disabled with "Coming in Phase 2" tooltips.
- TIER 1 / dev-only fallback: store reads VITE_OPEN_CODESIGN_DEV_KEY
so the demo runs before wt/onboarding lands real keychain plumbing.
Marked clearly for removal in the integration commit.
- examples/calm-spaces: README documents the demo + expected behaviour
+ intentional loud failure modes.
No new third-party dependencies. All UI uses var(--color-*) tokens.
Acceptance test (manual):
VITE_OPEN_CODESIGN_DEV_KEY=sk-ant-... \
pnpm --filter @open-codesign/desktop dev
→ click "Calm Spaces meditation app" → Send → iframe renders
→ Export → HTML → /tmp/out.html → open in browser
→ Export → PDF → toast "PDF export ships in Phase 2"
Signed-off-by: Haoqing Wang <1506751656@qq.com>
…p-lib Replaces the EXPORTER_NOT_READY stubs with three lazy-loaded tier-1 implementations decided in docs/research/04-pptx-export.md: - PDF: puppeteer-core (~12 MB) drives the user's installed Chrome. We refuse to bundle Chromium (PRINCIPLES §1: 80 MB cap; Chromium alone is ~150 MB). chrome-discovery.ts walks the canonical paths per-OS plus a CODESIGN_CHROME_PATH override and throws EXPORTER_NO_CHROME with the install URL when nothing is found — no silent fallback (PRINCIPLES §10). - PPTX: pptxgenjs (~2.6 MB). One slide per top-level <section>; whole document if none. Defaults emit bodyPr wrap=square and fit=shrink, sidestepping the dom-to-pptx CJK overflow bug (research/04 §"Known traps"). dom-to-pptx itself stays out of tier 1 — adding an unmaintained dep for the small subset of layouts it covers would violate "no bloat". - ZIP: zip-lib (~100 KB, MIT, zero deps). Stable layout: index.html at root, README.md with a generated banner, optional assets/ subtree. No streaming-write hooks for tier 1. All three exporter functions use `await import(...)` inside the function body, so cold-start does not pull any of these into the module graph. Verified via grep across packages/exporters/src/*.ts. Toolbar + IPC handler updated to flip readiness on PDF/PPTX/ZIP and swap the "Coming in Phase 2" tooltip for per-format hover-help. Tests: 18 new vitest cases — chrome discovery for mac/win/linux + override + missing, mocked puppeteer happy/override/error paths, PPTX extractSlides + a real CJK end-to-end write that PowerPoint opens, ZIP multi-asset round-trip via Unzip. Signed-off-by: hqhq1025 <1506751656@qq.com>
Collaborator
Author
|
Superseded by v2 (clean cherry-pick on top of latest main). See 7-v2 PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces the
EXPORTER_NOT_READYstubs inpackages/exporters/src/{pdf,pptx,zip}.tswith three lazy-loaded tier-1 implementations. Decisions locked indocs/research/04-pptx-export.md.Why each format works the way it does
PDF —
puppeteer-core+ system Chromechrome-discovery.tswalks canonical paths per OS (Mac.app, Win%ProgramFiles%, Linuxwhich) plus aCODESIGN_CHROME_PATHoverride.CodesignError('System Chrome not found, install from https://www.google.com/chrome', 'EXPORTER_NO_CHROME')when nothing resolves — no silent fallback (PRINCIPLES §10).Letterpage format (autoavailable for tall single-sheet output).PPTX —
pptxgenjsonly<section>; whole document if no sections exist.dom-to-pptxin tier 1 — unmaintained (last release ages ago), only adds value for the small set of layouts already covered by our regex extractor. Tier 2 can revisit.bodyPr wrap="square"and we explicitly setfit: 'shrink'(=normAutofit), sidestepping the dom-to-pptx CJK overflow bug from research/04 §"Known traps".ZIP —
zip-libindex.htmlat root,README.md(generated banner with timestamp + restore instructions), optionalassets/subtree.Zip.addFilealways points at a real file path (zip-lib's API takes disk paths, not buffers).Files
New
packages/exporters/src/chrome-discovery.ts+.test.ts(6 cases: mac path, win ProgramFiles, linuxwhich, override, missing, linux-missing)packages/exporters/src/pdf.test.ts(3 cases — happy / override / wrapped failure, all puppeteer mocked)packages/exporters/src/pptx.test.ts(6 cases incl. real CJK end-to-end write that produces a valid.pptxon disk)packages/exporters/src/zip.test.ts(3 cases incl. multi-asset round-trip viaUnzip)Modified
packages/exporters/src/{pdf,pptx,zip}.ts— real implspackages/exporters/src/index.ts—isExporterReadyflips totrue; dispatch passeshtmlContent+destinationPathpackages/exporters/package.json— three new prod depsapps/desktop/src/main/exporter-ipc.ts— comment refresh; routing already supported all four formatsapps/desktop/src/renderer/src/components/PreviewToolbar.tsx—ready: trueon PDF/PPTX/ZIP; tooltips describe what each format producesNew production dependencies
du -sh)puppeteer-core@^24await import('puppeteer-core')insideexportPdfbodypptxgenjs@^3.12await import('pptxgenjs')insideexportPptxbodyzip-lib@^1.0.4await import('zip-lib')insideexportZipbodyAll three are runtime deps but live entirely outside the cold-start module graph — the renderer never touches them, and the main process only resolves them the first time the user clicks Export → {PDF|PPTX|ZIP}.
Install size
du -sh release/was not measured because the desktop build pipeline is a follow-up (noelectron-builderconfig landed yet in this worktree). The on-disk dep contribution is bounded above bydu -sh node_modules/.pnpm/{puppeteer-core,pptxgenjs,zip-lib}= 14.7 MB unpacked. After Electronasarpacking + tree-shaking on import-time-only modules, expect ≤ 6 MB shipped. Re-confirm in the install-size CI gate once it's wired.Acceptance test outcomes
pnpm dev→ Export PDF → opens in Preview/Acrobat (manually verified locally on macOS with installed Chrome)npm vitest run packages/exporters/src/pptx.test.tsconfirms PK\x03\x04 magic bytes + bytes > 1 KBUnzipround-trip inzip.test.tsconfirmsindex.html,README.md, andassets/*are all presentpptx.test.tswrites<section><h1>你好世界</h1><p>第一张幻灯片</p></section>to a real file; manual PowerPoint Mac open shows no overlapchrome-discovery.test.ts"throws EXPORTER_NO_CHROME with install link" passes; loudCodesignError, no fallbackCI checklist
pnpm -r typecheck— greenpnpm lint— green (Biome, no warnings)pnpm -r test— 18 new + all existing tests pass§5b checks
ExporterFormatenum unchanged, IPC payload unchanged,exportArtifactsignature unchangedextractSlidesis the seam where dom-to-pptx will plug in; chrome-discovery'sChromeDiscoveryDepsis the seam for tests/electron-managed Chromium laterSigned-off-by: Haoqing Wang 1506751656@qq.com