Wocart

The manifest

A ThemeDefinition mixes two kinds of information: field declarations and defaults beside live Astro components and server-only load functions. A tool outside Astro cannot read that without running the theme.

The manifest is the data-only half: a JSON menu of every component, field, slot and surface the theme offers. An editor, a CLI or a marketplace can read it without importing Astro files. If a thing is not in the manifest, another tool cannot discover it.

You do not need the manifest to render a fixed Astro storefront. You need it when the theme must be edited, shared or checked by another program.

One source

The manifest is derived, never authored:

TypeScriptCode
import { themeManifest } from "@wocart/theme"; const manifest = themeManifest(theme);

It is stable across a JSON round trip. There is exactly one source of truth: the theme's definitions. A hand-written manifest can be validated, but it is never treated as the catalog the theme renders.

Emit it from the build

A theme's definition imports .astro files, so it can only be loaded inside Astro's build. Emit the manifest as a prerendered page:

TypeScriptCode
// src/pages/wocart-theme.json.ts import { themeManifestEndpoint } from "@wocart/theme/astro"; import { theme } from "../theme/theme"; export const prerender = true; export const GET = themeManifestEndpoint(theme);

The manifest is served at /wocart-theme.json. It is validated when the module loads, so an invalid manifest fails the build instead of shipping.

What it contains

JSONCode
{ "contractVersion": 1, "key": "my-theme", "version": "1.0.0", "sdkVersion": "4.1.0", "surfaces": [], "areas": [], "locales": ["ar", "fr"], "settings": [], "content": {}, "preview": false, "components": { "hero": { "type": "hero", "label": { "ar": "الواجهة", "fr": "Hero" }, "fields": [], "slots": [], "presets": [] } } }
FieldMeaning
contractVersionManifest schema and interpretation contract; currently 1
key, versionThe theme's identity and version
sdkVersionThe @wocart/theme that generated the manifest
surfaces, areasThe surfaces and shared areas the theme declares, each with its default document, required, max and, for a surface, pageType and hideableAreas
locales, contentDeclared languages and their content packs
settings, styleStore-wide settings and the step sizes
previewWhether the theme serves a preview route
componentsThe serializable component specs; no component, no load

Validate a manifest from outside

TypeScriptCode
import { validateThemeManifest } from "@wocart/theme"; const { manifest: checked, issues } = validateThemeManifest(input);

A structural failure returns manifest: null and issues. A non-semver version is advisory: the manifest is returned with an invalid-version issue. Every issue has a path into the manifest and one of:

invalid-manifest, unsupported-contract, invalid-identity, invalid-version, invalid-surface, invalid-settings, invalid-component, invalid-content, type-mismatch or non-serializable.

Validation mirrors assembly. Relational rules — contentKey/default exclusivity, a number default within its range, a select default in its options, slot-name shape, key/type agreement — are checked the same way as validateThemeDefinition.

Use it as an editing catalog

TypeScriptCode
import { manifestCatalog } from "@wocart/theme"; const catalog = manifestCatalog(manifest);

manifestCatalog turns a manifest into the shape every data function accepts: createInstance, instantiatePreset, the document operations, checkPageData, validatePageData, resolveThemeSettings and checkThemeSettings.

Its components have component: null. It describes and checks; it does not render. Do not pass it to InstanceView.

Manifest guarantees

PromiseConsequence
Data-onlyNo functions, symbols, bigints, non-finite numbers or cycles; those are non-serializable
Normalizedfields and slots are always arrays; absent means []
Key equals typecomponents[type].type === type
Unknown keys strippedAn older reader ignores newer metadata instead of failing
Validated at buildA theme that assembles is a theme whose manifest validates

The forward-compatibility tradeoff is deliberate: old readers stay green, but they cannot see new metadata until they update. That is why additive fields must never change how an existing field is read.

Change a document

The operations are the writer's API:

TypeScriptCode
import { checkPageData, removeInstance } from "@wocart/theme"; const edit = removeInstance(theme, page, "faq-1"); if (!edit.ok) throw new Error(`${edit.issue.code}: ${edit.issue.message}`); const issues = checkPageData(theme, edit.page);
OperationResult
insertInstance(theme, page, instance, placement)Add at a location
moveInstance(theme, page, id, placement)Move to another location or index
duplicateInstance(theme, page, id, newId)Copy right after the original, every copied instance with an id from newId
removeInstance(theme, page, id)Delete, unless required or fixed
setInstanceProps(theme, page, id, changes)Merge props, each checked by its field; undefined removes a stored value
setInstanceHidden(theme, page, id, hidden)Hide or show

They return { ok: true, page } or { ok: false, issue }, never mutate the input, and leave the rest of the document untouched. A refused edit says why: not-available, not-placeable, slot-full, required-block, fixed-block or cycle.

completePage(theme, page) adds missing required and fixed blocks. An editing tool starts from it so it never shows an incomplete page.

Compare two releases

TypeScriptCode
import { diffThemeManifests } from "@wocart/theme"; const { breaking, additive } = diffThemeManifests(previous, next);

Breaking means a saved document, setting or text edit may no longer read as it did:

  • a component, surface, area, field, slot, option or content key was removed,
  • a field kind changed,
  • a page type changed,
  • an allow list, range, list bound or rich-text format narrowed,
  • a slot became single,
  • a field became required with no default or content fallback,
  • a fixed block was removed or changed type,
  • a component stopped being insertable, or is allowed on fewer surfaces,
  • a surface's or area's max was lowered,
  • a style size the theme gave a step or width was removed,
  • a language was dropped from locales,
  • the theme key changed.

Production and upgrades has the same list as a table.

Everything else is additive. Saved data is never migrated: the reader degrades with reports, and this diff tells you beforehand what will degrade.

Versioning

Three contracts version independently:

VersionWhat it coversWhen it changes
contractVersionManifest schema and how consumers interpret itOnly a breaking change to that shape; currently 1
theme versionThe theme itselfSemver; a breaking diff needs a new major, and Wocart Cloud refuses a release that is breaking under the same major
PageData.versionThe page-document envelopeCurrently 1

A non-semver theme version is reported, not rejected: registration stays possible, but version hygiene is visible.

Do not hand-edit a shipped manifest

The manifest is a build artifact. Change a definition and rebuild. A manifest edited by hand drifts from the code that renders the theme.