banner_studio/package.json
Simeon Schecter bea0392d9c Day 4 of the vertical slice: render + export gate
Adds the render-and-export half of the slice: a Playwright-based render
worker that takes resolved BannerSpecs, builds self-contained HTML5 ad
units (absolute-positioned DOM, GSAP timeline, IAB clickTag, baked
typography + fonts), captures a backup PNG, and packages each into a zip
on disk. A new POST /api/export route drives it from /review via an
"Export ZIPs (N)" button.

What ships
- packages/render-worker (new package, promoted from the apps/ placeholder)
  - build-runtime-html: pure function producing the self-contained
    index.html. Inlines @font-face for Inter Regular/Bold, the resolved
    spec as JSON, GSAP's minified bundle, and a runtime IIFE that mirrors
    build-timeline.ts semantics (fade_in / fade_out / hold + padding tween).
    Layers with a fade_in event ship with opacity:0 inline to avoid a
    first-frame flash.
  - render-to-zip: stages temp dir, opens Playwright file://, awaits
    document.fonts.ready, pauses window.__tl and seeks to t=1.0s,
    screenshots backup.png, assembles JSZip on disk under
    exports/<safe(campaign_id)>/<safe(version_id)>.zip.
  - render-many: single Chromium, min(N,3) concurrent contexts, fonts
    + GSAP read once. Returns { exported, errors } so a single bad spec
    doesn't kill the batch.
  - safe-slug: sanitises path segments to [A-Za-z0-9_-], 'unknown' fallback.
  - 14 tests (8 build-runtime-html + 6 safe-slug). All green.
- apps/web/app/api/export/route.ts: validates each incoming spec with
  BannerSpecSchema (failures land in the errors list, not a 400), dynamic-
  imports the render-worker, calls renderMany, returns paths + byte sizes.
- apps/web/app/review/ReviewClient.tsx: export state machine (idle |
  exporting | done | error), "Export ZIPs (N)" button beside Play-All,
  success summary listing zip paths.
- apps/render-worker/ deleted; superseded by the package shape.

dropflow wasm wiring (Day-3 bug that surfaced on Day 4)
dropflow 0.5.1's wasm.js does `await environment.wasmLocator()` at module
top level. In Next.js 14.2.x, webpack's experiments.topLevelAwait flag is
ignored (regression vs 14.1.x), so any locator install that runs after
the dropflow chunk loads is too late — wasm.js's default locator throws
"Wasm location not configured" at module-init time.

Fix is a small pnpm patch on dropflow/dist/src/environment-browser.js,
which is imported by api.js BEFORE wasm.js via the `#register-default-
environment` browser-condition subpath. The patch installs a wasmLocator
that fetches /dropflow.wasm at exactly the right point in dropflow's
own initialization. patches/dropflow@0.5.1.patch is committed and wired
through pnpm.patchedDependencies.

Supporting changes in the layout-engine to keep the static module graph
dropflow-free where possible:
- browser/wasm-locator.ts: dynamic-imports dropflow/environment.js.
- browser/dropflow-wrapper.ts: dynamic-imports dropflow itself; uses an
  inline structural type instead of `import type * as DropflowNs`.
- core/measure.ts: removed eager dropflow import; receives the namespace
  via setDropflow() from whichever wrapper inits first.
- apps/web/lib/engine.ts: dynamic-imports the layout-engine browser
  barrel so dropflow doesn't enter /review's synchronous graph.
- BannerCanvas.tsx + ParityClient.tsx: pull DEMO_TEMPLATE_300x250 from
  the dropflow-free @banner-studio/layout-engine/templates subpath
  rather than /browser. ParityClient also dynamic-imports resolveLayout.

Other plumbing
- next.config.mjs: server externals for playwright / playwright-core /
  chromium-bidi / jszip / @banner-studio/render-worker so webpack doesn't
  try to bundle dynamic CJS requires it can't trace.
- SLICE_DEVIATIONS.md: added §6 (render-worker as a package not an app),
  §7 (hero image remote-referenced, not bundled), §8 (CM360 click-macro
  deferred — IAB clickTag only).

Acceptance gate (Day 4)
- pnpm -r test: 40 tests across 4 packages green.
- pnpm -r build: clean, /api/export present in the Next route table.
- Manual smoke: /review → Export ZIPs → real .zip files in
  exports/demo-campaign/. Unzipped index.html opens in Chrome, animates,
  and the clickTag opens example.com/<row>.url in a new tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-16 13:29:01 -04:00

27 lines
576 B
JSON

{
"name": "banner-studio",
"private": true,
"version": "0.0.0",
"scripts": {
"build": "turbo run build",
"test": "turbo run test",
"dev": "turbo run dev",
"prepare:web-assets": "node scripts/prepare-web-assets.mjs"
},
"devDependencies": {
"csv-parse": "^5.5.6",
"tsx": "^4.19.0",
"turbo": "^2.0.0",
"typescript": "^5.4.0",
"vitest": "^1.6.0"
},
"packageManager": "pnpm@9.0.0",
"engines": {
"node": ">=20"
},
"pnpm": {
"patchedDependencies": {
"dropflow@0.5.1": "patches/dropflow@0.5.1.patch"
}
}
}