banner_studio/apps/web/scripts/prepare-parity-baseline.ts
Simeon Schecter 988a47c797 Initial commit: Day 1 + Day 2 of the vertical slice
Day 1 (monorepo + Node layout engine):
- Turborepo + pnpm workspaces with apps/web, apps/render-worker, and
  packages for types, layout-engine, prompts, api-lib.
- @banner-studio/types: BannerSpec contract, every layer kind, ResolvedLayer,
  zod schemas mirroring each interface.
- @banner-studio/layout-engine: Dropflow WASM wrapper, text measurement,
  shrink-to-fit, push_siblings, resolveLayout. Snapshot-tested.

Day 2 (browser parity + AI pipeline):
- Layout engine ./browser subpath: same resolveLayout in the browser via
  Dropflow WASM build. Quarantined wasm-locator import (dropflow 0.5.1
  exports gap).
- Cross-group push_siblings bug fix: deltas now thread through group
  recursion via a shared accumulator; regression test added.
- DEMO_TEMPLATE_300x250 promoted to packages/layout-engine/src/templates/.
- @banner-studio/prompts: versioned extract + generate prompts with
  zod-defined tool schemas (claude-sonnet-4-6, forced tool-use).
- @banner-studio/api-lib: CSV feed loader, extract/generate/route-node/
  assemble agents, orchestrator returning fully-resolved BannerSpec.
  Generate agent retries on character-limit overflow.
- apps/web (Next.js 14 App Router): /api/generate route, /parity diff page,
  promise-singleton browser engine init.
- feeds/demo.csv with five hand-authored rows of varied length.
- SLICE_DEVIATIONS.md documents the five intentional gaps from
  ARCHITECTURE.md with V1 reversal paths.

Verified end-to-end: POST /api/generate against the live Claude API
returns three resolved BannerSpecs and two honestly-skipped rows
(overflow after two attempts). 26 unit + integration tests passing.
2026-05-15 10:25:21 -04:00

65 lines
1.7 KiB
TypeScript

// Generates apps/web/lib/parity/baseline.json by running the Node layout
// engine against DEMO_TEMPLATE_300x250 with a canonical copy map. Commit
// the result; the /parity page diffs against it.
import path from 'node:path';
import { mkdir, writeFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import {
DEMO_TEMPLATE_300x250,
initLayoutEngine,
resolveLayout
} from '@banner-studio/layout-engine';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, '..', '..', '..');
const fontsDir = path.join(repoRoot, 'infra', 'fonts');
const outDir = path.join(__dirname, '..', 'lib', 'parity');
const outFile = path.join(outDir, 'baseline.json');
export const CANONICAL_COPY = {
headline: 'Hello world',
subheadline: 'Subtitle line',
cta: 'Shop now'
};
async function main() {
await initLayoutEngine({
fonts: [
{
family: 'Inter',
weight: 400,
path: 'file://' + path.join(fontsDir, 'Inter-Regular.ttf')
},
{
family: 'Inter',
weight: 700,
path: 'file://' + path.join(fontsDir, 'Inter-Bold.ttf')
}
]
});
const artboards = resolveLayout(DEMO_TEMPLATE_300x250, CANONICAL_COPY);
await mkdir(outDir, { recursive: true });
await writeFile(
outFile,
JSON.stringify(
{
copy: CANONICAL_COPY,
artboards,
captured_at: new Date().toISOString(),
captured_with: 'node @banner-studio/layout-engine'
},
null,
2
),
'utf8'
);
console.log(`wrote ${path.relative(repoRoot, outFile)}`);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});