@dani-builder/site-templates
Portable scaffold initial CMS content for the business-basic template and
open-atelier theme, originally based on Figma v3.
forest-stay, logistics, and ceramics select composition; pine, ocean, and
clay select colors independently. User brand colors override palette colors,
which override theme defaults. Selecting a palette never changes seed data.
The packaged seed data supplies initial CMS content during scaffolding. External
showcases own their copy, photos, FAQs, navigation, footer, block placement, and
Figma content conformity. They can author a TemplateSeed, validate it with
collectSeedValidationErrors(manifest, compositionId, seed), and pass it as the
optional seed to createTemplatePreview. The preview validates the supplied seed against the supported Builder contract
and keeps strict manifest validation for the other compositions.
Omitting seed (or passing undefined) uses the packaged composition seed. Builder owns the shared rendering and seed contracts; renderer,
block, token, and schema defects return to Builder. See ADR 0008
and the authoring example.
| Contract | Previous example | Final v3 bundle |
|---|---|---|
| Template version | 0.1.0 | 0.2.4 |
| Routes / blocks | 9 / 27 generic blocks | 9 / 41 source blocks (13 / 14 / 14) |
| Photos / bindings | 4 placeholder paths | 15 packaged JPEG scenes / 23 bindings |
| FAQ documents | 2 shared string answers | 9 industry FAQs with ordered BlocksContent and sortOrder |
| Shells | Generic Open Atelier | solgyeol / haeon / heukbit |
The npm package version is release-managed separately from the template version.
Any change to the manifest, portable seeds, or packaged asset bytes requires a
new template version and a new entry in test/template-fingerprints.json. Keep
previous fingerprint entries unchanged. test/template-version.test.ts enforces
this version and fingerprint rule. When adding seed attributes, recheck
builderVersion against the release that introduced those attributes.
Builder compatibility is >=0.16.0 <3 because seeded homes use Page.directAccess, introduced in Builder 0.16.0. requiredComponents and requiredVariants
are derived from the final seed, rather than the complete Figma block gallery.
The client dependency uses workspace:^, published as a caret range: compatible
Builder/client fixed-group patches do not release this package or the CLI. A 0.x
client minor crosses that range and still triggers template and CLI patches. An
explicit template update also releases the CLI, whose template dependency remains
exact. Introducing this dependency policy releases templates and the CLI once.
import {
getSiteTemplate,
collectSeedValidationErrors,
collectTemplateValidationErrors,
} from '@dani-builder/site-templates';
const template = getSiteTemplate('business-basic');
const seed = template.compositions['forest-stay'].seed;
const colors = template.palettes.clay.brandColors;
const errors = collectTemplateValidationErrors(template);
// For externally authored content, validate only that supplied TemplateSeed:
const seedErrors = collectSeedValidationErrors(template, 'forest-stay', seed);
Each page stores route: home has slug home but resolves to /; the other routes
are /about and /contact. Preserve block order, text, explicit nulls, empty
ctaText, and section anchors. Contact Heroes intentionally have null image and
both links, with block headerMode: 'none'. Feature links may be null. Every
Highlights item requires an image; every non-null shared image has
{ media: { assetKey }, alt, fit }. Direct upload fields such as footer/navigation
logos use a bare { assetKey } or null. Assets support JPEG (image/jpeg) and
PNG (image/png), including transparent PNG branding; SVG is excluded from the
portable seed contract. Optional global.favicon uses the same bare reference.
Omitted or null favicon values project to null. Preview media preserves the MIME
and dimensions, deriving .jpg or .png from the MIME. All sample logos remain
null: use the site name as the text brand fallback.
FAQ blocks contain ordered { relationKey } references to seed.faqs; answers
and content bodies use the existing paragraph/text BlocksContent shape.
Descriptions in Features and Highlights retain the existing richtext string
contract. No CMS numeric IDs, document IDs, publication timestamps, Figma hashes,
viewport state, form placeholders, or authoring envelopes are seeded.
navigation.groups maps to the existing CMS globalNavigation field. Group
href, child links, primaryLink, secondaryLink, mainLogo, darkLogo, and
align map directly to their existing schema fields. Group links are empty for
the two simple menu entries. The primary action targets /contact#inquiry; menu
links preserve /#services and /about. Footer organization holds the industry
wordmark (solgyeol, haeon, or heukbit), matching global.sitename; Korean
industry names remain composition titles. businessInfo preserves description
and sample-brand legal notice as ordered lines. sitemap has two ordered groups:
둘러보기 with home/about/contact and 문의하기 with the single mailto: contact.
otherLinks and socialLinks are empty. This uses the existing footer schema.
The current shared Footer places businessInfo in its contact area; the seed does
not introduce presentation fields for Figma's separate description/copyright positions.
Version 0.2.1 also removes the Pine and Clay home/about CTA body text absent from
the confirmed Figma v3 nodes; those four ctaText values are null. All remaining
copy, destinations, 15 scenes, 23 media bindings, 41 blocks and nine FAQs retain
the confirmed order and content. Existing applied 0.2.0 selections remain an
explicit version conflict on import; use a fresh scaffold or consistent backup
rather than overwriting customer edits.
All photos are fictional-brand illustrative samples under the recorded Unsplash license. Asset metadata contains the exact source URLs, author, license URL, alt, sample notice, MIME and actual dimensions. Source URLs are attribution metadata; runtime rendering can work offline with the packaged files.
Copy the packaged assets/business-basic directory into your host's
public/site-templates/business-basic directory. For each asset, resolve
asset.src by replacing assets/ with /site-templates/; for example
assets/business-basic/pine-cabin.jpg becomes
/site-templates/business-basic/pine-cabin.jpg. The public subpath export
@dani-builder/site-templates/assets/business-basic/pine-cabin.jpg allows tools
to locate each packaged file through import.meta.resolve() without a root
filesystem assumption. files includes all assets in npm tarballs.
DEV-257 uploads/reuses the files in Strapi, resolves logical media/FAQ/page keys, maps navigation groups, applies locale-aware links and writes CMS documents. DEV-270 converts this seed into a CMS-free preview read model and supplies its asset URL resolver. Renderer behavior, form submissions, CLI/importer execution, and static-browser verification belong to those follow-up tasks.
Static frontend preview
The ./preview entry projects the same portable seeds into the existing
@dani-builder/strapi-client/types frontend contracts. It performs no fetches,
CMS writes, or environment lookups and needs no Strapi or Next.js runtime.
import { businessBasicTemplate } from '@dani-builder/site-templates';
import { createTemplatePreview } from '@dani-builder/site-templates/preview';
// showcaseSeed is a TemplateSeed authored by your host project.
import { showcaseSeed } from './content.js';
const preview = createTemplatePreview({
manifest: businessBasicTemplate,
seed: showcaseSeed, // Optional; defaults to the selected composition seed
selection: {
templateId: 'business-basic',
templateVersion: businessBasicTemplate.version, // Not the npm package version
themeId: 'open-atelier',
compositionId: 'forest-stay',
paletteId: 'clay',
},
builderVersion: '0.16.0', // Explicit target Builder version
resolveAsset: (asset) => ({
url: `/static/templates/${asset.src}`,
// Optional width, height, alternativeText override registry metadata.
}),
brandColors: { primary: '#123456', text: null }, // null falls back to palette
});
const home = preview.pages.find(({ route }) => route === '/')?.page;
// Pass home.blocks to BlockRenderer; preview.navigation/global/footer to your shell.
pages contains ordered { route, page: PageDocument } entries. The result also
includes FaqDocument[], NavigationDocument, GlobalDocument, Footer, the
selected TemplateTheme, BrandColors, and a copy of the selection. FAQ relations
retain seed order; global.home contains the referenced page. Navigation groups
become globalNavigation. Links retain hrefs, anchors, labels and newWindow;
a logical page link without an href resolves to its page route. The host's Link
adapter owns preview prefixes and locales, and handles internal paths, fragments,
mailto:, tel: and external URLs without rewriting seed data.
The resolver is synchronous and receives a separate asset copy. Return a root
relative or HTTP(S) URL, with optional positive integer dimensions and nullable
alternativeText. Registry dimensions/alt are the defaults; empty alt remains
intentional. Per-use shared.image.alt stays separate from upload alternativeText.
Resolver errors, invalid metadata, unknown IDs, mismatched template versions,
unsupported Builder versions, broken or wrong-kind refs, and invalid Builder
components fail with an error. npm semver ranges include normal prerelease rules.
The supported portable block families are hero, content, highlights, features,
CTA, FAQ, inquiry-form, slider, bento-grid, testimonials, pricing, stats, team,
and logo-cloud. The added families use the complete existing Builder schemas:
| Block | Supported variants / columns |
|---|---|
| slider | cover-flow, card-slider |
| bento-grid | 2, 3 (default), 4 columns |
| testimonials | centered, side-by-side, card-grid, subtle-grid |
| pricing | two-column, three-column, single, with-comparison |
| stats | simple, card-grid, split |
| team | grid, large-image, compact (photo optional) |
| logo-cloud | inline, grid, split, marquee |
| CTA | inline-spread, inline-centered, inline-stacked, card-stacked, card-centered, card-horizontal |
Slider items require a shared image with media; logo items require logo media.
Testimonials require quote, pricing tiers require planName and price, stats
require value, and team members require memberName. Optional media can be null.
Bento spans and columns and testimonial ratings enforce schema integer ranges.
Nested repeatable fields, links, media references, enums and defaults use the same
contract as the existing blocks, and the resulting DTO goes directly to BlockRenderer.
Validation is shared through collectSeedValidationErrors and
collectTemplateValidationErrors, without CMS IDs. The standalone seed validator
checks only its supplied seed against the supported Builder component and enum
contract, independently of the manifest's requiredComponents and requiredVariants.
Those lists continue to constrain the manifest's own seeds through
collectTemplateValidationErrors. Its string composition ID labels errors and does
not add a composition to the manifest. Preview selection and Builder version checks still
apply when a seed override is supplied. Neither API mutates the manifest or seed.
Optional component/media/link fields normalize to null, repeatable fields to empty arrays, and schema defaults are applied when absent. Nonnullable frontend variant fields use the first schema enum when absent/null; CTA keeps nullable variants. Required image media and highlight images cannot be null. User colors win over palette colors, which fall back to the theme default palette. Changing palette changes only selection and colors.
Identities are preview-only, never suitable for CMS writes. documentIds use
preview:<template>:<version>:<composition>:<logical-path>. Numeric IDs are negative
and allocated deterministically by logical path in stable seed traversal order,
without palette input or hashes. Media uses provider: 'template-preview', no
formats, size 0 (unknown), and fixed 2000-01-01T00:00:00.000Z timestamps. Copies
of the same logical document keep their IDs. The entire result is isolated from
input and JSON round-trippable; no resolver functions or registries are included.