@frank/ad-template (0.2.0)

Published 2026-10-04 23:31:54 +11:00 by steve

Installation

@frank:registry=
npm install @frank/ad-template@0.2.0
"@frank/ad-template": "0.2.0"

About this package

@frank/ad-template

The sole shared layered ad-template contract, ported unchanged from packages/ad-template-contract in the Blockwise repo. It defines the AdTemplate and AdDocument shapes, the Zod schemas that validate them at trust boundaries, and the render-primitive constants (placements, semantic colour roles, icon names, minimum text sizes) that renderers and editors must agree on. The schema string literals (blockwise.ad-template, blockwise.ad-document) are wire-format identifiers and stay as they are.

Public API (src/index.ts)

  • Types: AdTemplate, AdDocument, Layout, LayoutLayer, Rect, Placement, ColourRole, SupportedIconName, NativeEditorDocument, and the metadata/publish-requirement types.
  • Schemas: adTemplateSchema, adDocumentSchema, nativeEditorDocumentSchema, nativeFabricSceneSchema.
  • Constants: COLOUR_ROLES, LAYER_TYPES, SUPPORTED_ICON_NAMES, PLACEMENTS, PLACEMENT_DIMENSIONS, MINIMUM_TEXT_SIZE_PX, MINIMUM_MULTILINE_LINE_HEIGHT, MINIMUM_VECTOR_LINE_LENGTH_PX.

Config

None. The package reads no environment and touches no network or database.

Usage

import { adTemplateSchema, type AdTemplate } from "@frank/ad-template";

const parsed = adTemplateSchema.safeParse(JSON.parse(artifactJson));
if (!parsed.success) throw new Error("invalid template artifact");
const template: AdTemplate = parsed.data;

Consumers: @frank/ad-template-renderer and, after slice 3 cutover, the Blockwise app.

Project-scoped creative requests

The existing template/document wire formats remain unchanged. The additive frank.creative.template-render.v1 request contains scope, requestId, template, document, imageAssets, optional fontAssets and optional colourOverrides. scope is { workspaceId: UUID, projectId: safeIdentifier }. Each asset is { scope, assetId, versionId, mimeType }; references are immutable project-owned versions, never paths or signed URLs. Images use the renderer's existing input/asset keys; document image values must identify those assets. All image and font references must match both workspace and project before I/O. Shared presets must be explicitly adopted into an authorized project first.

Named public helpers include parseTemplateRenderRequest, assertScopeMatches, scopeMatches, renderInputImageKeys, resolveColourMap, projectObjectKey, stableStringify, payloadDigest, planTemplateRenderOutputs and createTemplateRenderJob. The last three are async. SHA-256 uses platform Web Crypto; importing the package performs no I/O and the source graph bundles for a browser without Node builtins.

resolveColourMap returns a copy of exactly the document's persisted resolvedColourMap plus any explicit colourOverrides (overrides win per key), and nothing else. It maps the document dictionary straight through: named CSS strings, functional rgb(...) strings, unsupported strings and keys no layer role consumes are all preserved verbatim — never filtered, converted or given a default — because the persisted document map keeps the existing adDocumentSchema acceptance (a Record<string, string> of non-empty strings). Its return type is that same Record<string, string> dictionary. Template semanticColours are not merged in, and a role the document omits stays omitted, so the renderer's canonical per-layer fallbacks then apply (text #000000, plate #FFFFFF). An app that wants template defaults must resolve them into document.resolvedColourMap before enqueuing the request.

The request's explicit colourOverrides are a separate, strict contract: only the six semantic roles, each a #hex string; an unknown role or a non-hex value is refused.

import { createTemplateRenderJob } from "@frank/ad-template";
import { enqueueQueuedJob } from "@frank/jobs";

// The app authorizes this workspace/project and resolves immutable asset refs.
const descriptor = await createTemplateRenderJob(request);
const job = await enqueueQueuedJob(service, descriptor);

creative.template.render descriptors carry the normalized payload, workspace fence, three attempts and a scope/content-sensitive dedupe key. Outputs use <workspace>/projects/<project>/template-renders/<requestId>/ plus <payload-sha256>-feed.png and <payload-sha256>-story.png. Reordered object keys produce the same plan; changed crops, text, colors or asset versions produce different paths even when requestId is reused.

The app must derive scope from authenticated project records, check asset permission and ledger MIME/version metadata, and compare the claimed job's workspace with payload.scope.workspaceId. Payload scope alone grants no permission. Customer workflow, pricing, entitlements and publishing remain in apps; authoring stays with Hermes.

Coordinator integration proposal

No existing manifests or lockfiles were changed. Root workspaces already match packages/*; the new @frank/video-tools needs a lockfile workspace entry. Before product consumption, the coordinator must choose new pinned versions for this package and the renderer, update renderer/video dependencies to that new contract version, and update app pins or local packaging. Suggested next versions are ad-template/renderer 0.2.0, video-tools 0.1.0; nothing is published here. New video-tools dependencies are this contract and existing zod ^3.24.0.

Dependencies

Dependencies

ID Version
zod ^3.24.0

Development dependencies

ID Version
@types/node ^22.0.0
Details
npm
2026-10-04 23:31:54 +11:00
89
latest
49 KiB
Assets (1)
Versions (2) View all
0.2.0 2026-10-04
0.1.0 2026-09-26