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.
65 lines
1.7 KiB
TypeScript
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);
|
|
});
|