@frank/ad-template (0.2.0)
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 |