Generator Runtime Contract V1
Goals
The Generator Runtime Contract defines how generators are embedded and run, ensuring that the same generator can run both as a full page and as an embedded module within a template page.
If you need the practical communication map between generator-workbench and the runtime, including routeMode, runtime events, and reverse commands, also read Workbench Runtime Communication.
It addresses:
- Supporting both
fullandembedmodes for the same generator. - Rendering only the central canvas and partial parameters in template pages.
- Reading, setting, restoring, and exporting generator state by the host.
- Tailoring parameter panels via unified schemas and filtering rules.
It does NOT address:
- Implementation of platform capabilities like login, cloud save, history, credits, billing, and export.
- CMS configuration, publishing orchestration, and approval flows.
- Specific business UI forms like top navigation, login modals, and sharing dialogs.
These remain the responsibility of generator-sdk and generator-control-plane.
Relationship with Refactoring Delivery
In the context of refactoring existing generators, it's important to distinguish between these two types:
- Compatibility Refactoring: Prioritizes non-regression and minimal changes, allowing for partial runtime or adapter implementation.
- Standardization Refactoring: Ultimately converges the old generator into a standard generator structure.
The Generator Runtime Contract is a necessary condition for Standardization Refactoring, but not a sufficient one.
This means that the following scenarios cannot be equated to "Standardization Refactoring Completion":
- Only adding
window.__GENERATOR_RUNTIME__. - Only adding
getState()/setState()/patchState(). - Only integrating the SDK and wrapping it with a lightweight bridge.
- Only implementing
PanelSchemawithoutfull/embedsupport.
To claim "Standardization Refactoring Completion," the following must also be present:
generator-sdkintegrated with all required platform capabilities.- Both
fullandembedentry points are functional. window.__GENERATOR_RUNTIME__is exposed.getPanelSchema()+PanelFiltercan be consumed by the host.- Unified template protocol used for template scenarios.
- Compatibility, migration, or CMS documentation is complete.
Boundaries
generator-sdk
Responsible for platform capabilities:
- Login
- Cloud Save / Restore
- History
- Credits
- Unified Billing
- Export to Local / Open in Studio
Generator Runtime Contract
Responsible for the runtime protocol between the host and the generator:
- Canvas mounting
- Parameter panel mounting
- State acquisition and setting
- Parameter schema exposure
- Parameter filtering
- Export data provision
- Runtime event subscription
Core Principles
Single Source of Truth for State The generator must be driven by a serializable
state. Both template and full pages use the same business state model.Decoupling Shell from Core Top navigation, sharing, login, and business overlays belong to the host shell; the canvas, parameters, and export provider belong to the generator runtime.
Parameter Panels Derived from Schema Template pages should not hardcode form logic for a specific generator; they should consume the
PanelSchemaexposed by the runtime.Predictable Embedding The core canvas result for the same
statemust be consistent acrossfullandembedmodes.Incremental Compatibility Old bridges are allowed to map to this protocol via adapters, but new generators must implement this protocol directly.
Completion Claims Restricted by Gates Even if the runtime is partially implemented, it should be described as a phased delivery unless all standardization criteria are met.
Mode Definitions
full
Full editor mode, typically including:
- Top navigation
- Left toolbar
- Central canvas
- Right-side full parameter panel
embed
Embedded mode, where the host page provides the business shell and the generator exposes only:
- Canvas
- Parameter panel or a subset of it
- State synchronization capabilities
- Export capabilities
Minimal Interface
V1 requires generators to expose at least the following interface:
type RuntimeMode = 'full' | 'embed'
type MountTarget = 'full' | 'canvas' | 'panel'
interface GeneratorRuntime {
version: string
generatorId: string
capabilities: RuntimeCapabilities
mount(options: MountOptions): MountedInstance | Promise<MountedInstance>
getState(): GeneratorState
setState(nextState: GeneratorState, options?: SetStateOptions): void | Promise<void>
patchState(patch: StatePatch, options?: SetStateOptions): void | Promise<void>
getPanelSchema(options?: GetPanelSchemaOptions): PanelSchema
export(action: ExportAction, options?: ExportOptions): Promise<ExportResult | null>
subscribe(listener: RuntimeEventListener): () => void
dispatchWorkbenchCommand?(command: WorkbenchCommand): void | Promise<void>
}This set of interfaces defines the minimal protocol surface for the runtime; it is not the same as the full delivery threshold. For existing generators, it often corresponds to "Phase 2: Completing Runtime Interfaces," not the end of all refactoring work.
mount()
Responsible for mounting the generator into the container provided by the host.
interface MountOptions {
mode: RuntimeMode
routeMode?: RuntimeMode
target: MountTarget
container: HTMLElement
state?: GeneratorState
panelFilter?: PanelFilter
readonly?: boolean
hostContext?: HostContext
}Requirements:
- Allow rendering the full editor when
mode: 'full'. - Support
canvasorpanelmounting whenmode: 'embed'. routeModereflects the actual route?mode=so the runtime can distinguish runtime mount mode from page capability mode.- In
generator-workbenchshellmode, the runtime can still receivemode: 'full'together withrouteMode: 'embed'. - Support repeated mounting and unmounting.
- Respond to container size changes.
getState() / setState() / patchState()
Responsible for state synchronization between the host and the generator.
getState()returns the current full business state.setState()is used for full state replacement.patchState()is used for partial updates, ideal for template pages controlling a few parameters.
getPanelSchema()
Returns the parameter panel description protocol, allowing the host to render all or partial parameters.
export()
Provides the export result; the host or generator-sdk decides how to download, open in Studio, or handle billing.
subscribe()
Subscribes to runtime events such as state changes, parameter schema changes, and errors.
dispatchWorkbenchCommand()
Receives workbench-side commands such as shell actions, route sync, or future host orchestration requests.
interface WorkbenchCommand {
type: string
data?: unknown
}This method is optional, but it is the recommended standard entry when generator-workbench or another host needs workbench -> runtime communication while keeping the runtime contract public and extensible.
Recommended Browser Registration
To ensure compatibility across plain HTML, iframe, and script scenarios, the following convention is recommended:
declare global {
interface Window {
__GENERATOR_RUNTIME__?: GeneratorRuntime
__registerGeneratorRuntime__?: (runtime: GeneratorRuntime) => void
}
}State Model Recommendations
Generators are not required to share the same rendering engine, but they are required to share a unified protocol shape.
interface GeneratorState {
meta: {
schemaVersion: string
generatorId: string
title?: string
updatedAt?: string
}
document: {
width?: number
height?: number
unit?: 'mm' | 'px' | 'in'
layers?: Array<Record<string, unknown>>
}
params: Record<string, unknown>
selection?: {
activeLayerId?: string | null
activeTool?: string | null
}
view?: {
zoom?: number
panX?: number
panY?: number
panelTab?: string
expandedGroups?: string[]
}
assets?: Array<Record<string, unknown>>
}Recommended Constraints
metaanddocumentshould be stable and usable for cloud save, restore, and template page rendering.paramsis used for parameter panel field binding.selectionandvieware allowed to be editor-state data and are not required to be fully consumed by template pages.statemust be JSON-serializable.
PanelSchema
Template pages should not hardcode form logic for each generator; they should consume the parameter schema returned by the generator.
interface PanelSchema {
version: string
generatorId: string
groups: PanelGroup[]
}
interface PanelGroup {
id: string
title: string
description?: string
order?: number
collapsible?: boolean
defaultExpanded?: boolean
fields: PanelField[]
}
interface BaseField {
id: string
label: string
type: string
bind: {
path: string
}
helpText?: string
readonly?: boolean
}Recommended Field Types
V1 recommends prioritizing these basic fields:
textnumbersliderselecttogglecolorimagecustom
If a generator has complex custom controls, use custom, but still expose its placeholder and binding information via the unified schema.
PanelFilter
When a template page renders only partial parameters, it must be clipped via PanelFilter rather than re-hardcoding logic.
interface PanelFilter {
includeGroups?: string[]
excludeGroups?: string[]
includeFields?: string[]
excludeFields?: string[]
readonlyFields?: string[]
hiddenFields?: string[]
orderOverrides?: Array<{ id: string; order: number }>
}Filtering Rules
- If a group is excluded, all its fields are invisible.
- If a group has no visible fields after filtering, it is automatically hidden.
includeFields/excludeFields/readonlyFieldsshould prioritize usingfield.bind.pathfor matching.readonlyFieldsonly changes interaction permissions, not field visibility.- The host must not directly modify the generator's internal field definitions; it can only clip them via the filter.
Template Definition Protocol
When a generator needs to support "Publish Template / Import Template," it is recommended to use the template module of generator-sdk rather than designing a private JSON structure for each generator.
interface GeneratorTemplateDefinition {
type: 'generator-template'
version: '1.0.0'
generatorId: string
appKey?: string
templateMeta?: Record<string, unknown>
defaults: Record<string, unknown>
panelFilter: PanelFilter
adjustableFields: Array<{
groupId: string
groupTitle?: string
fieldId: string
fieldLabel?: string
path: string
}>
metadata?: Record<string, unknown>
}Conventions:
defaultsis the default state snapshot of the template.panelFilteris the unique standard for the template consumption side; hosts should consume it directly rather than re-deriving field rules.adjustableFieldsis only for template authoring tool display and does not replacepanelFilter.- After importing a template, the host should restore template capabilities via
runtime.setState()/runtime.patchState()+panelFilter.
If the runtime already integrates the template protocol, it can optionally expose a capability flag:
capabilities: {
supportsTemplates?: true
}embed Mode Constraints
New generators in embed mode must follow these rules:
- Do not render top navigation.
- Do not render platform shell entries like login, sharing, and credits.
- Do not trigger billing independently; it should be triggered by the host in combination with
generator-sdk. - Do not rely on global
bodylayout for core rendering. - Do not hardcode page dimensions; must adapt to container size.
- Must support read-only mode.
- Core canvas results must be consistent between
fullandembedmodes for the samestate.
If an old generator can only run partially in embed mode but does not yet have an independent full entry point, it should still not be claimed as "Standardization Refactoring Complete."
Event Protocol
type RuntimeEvent =
| { type: 'ready' }
| { type: 'state-change'; state: GeneratorState; patch?: StatePatch; source?: string }
| { type: 'params_change'; data: { field: string; value: unknown; params?: Record<string, unknown> } }
| { type: 'panel-schema-change'; schema: PanelSchema }
| { type: 'select_template'; data: { name: string; category?: string } }
| { type: 'warning'; code: string; message: string }
| { type: 'error'; code: string; message: string }V1 Minimal Requirements:
- Trigger
readyonce after the initial mount. - Trigger
state-changeafter parameter modification. - If the runtime needs field-level parameter feedback, trigger
params_changeafter a panel parameter changes. - Trigger
panel-schema-changewhen schema visibility changes with state.
If the runtime needs to notify the host about additional business-side interactions, it can additionally emit:
params_changeselect_template
Rules:
params_change.data.fieldis required and represents the changed parameter field name.params_change.data.valueis required and represents the new field value.params_change.data.paramsis optional and can contain the current full params object.data.nameis required and represents the selected template name.data.categoryis optional and represents the template category.- When the runtime is hosted by
generator-workbenchand the page route is?mode=embed, the workbench forwards this runtime event to the parent page bridge asgenerator_toSelectTemplate.
Recommended pattern:
const listeners = new Set<(event: RuntimeEvent) => void>()
function emit(event: RuntimeEvent) {
listeners.forEach((listener) => listener(event))
}
function onTemplateCardClick(template: { name: string; category?: string }) {
emit({
type: 'select_template',
data: {
name: template.name,
category: template.category,
},
})
}
function onPanelFieldChange(field: string, value: unknown, params: Record<string, unknown>) {
emit({
type: 'params_change',
data: {
field,
value,
params,
},
})
}Workbench To Runtime Command Convention
For bidirectional communication, the recommended split is:
subscribe(listener)forruntime -> workbenchdispatchWorkbenchCommand(command)forworkbench -> runtime
Example:
runtime.dispatchWorkbenchCommand = async (command) => {
if (command.type === 'open-login') {
openLoginPanel(command.data)
}
}When this contract is used inside generator-workbench, the host-facing method name is workbench.dispatchRuntimeCommand(...). It is an adapter around the same direction: workbench -> runtime.
Export Protocol
type ExportAction =
| 'cover'
| 'download-svg'
| 'download-png'
| 'open-in-studio'
interface ExportResult {
dataUrl: string
mimeType: string
ext: string
width?: number
height?: number
metadata?: Record<string, unknown>
}Division of Responsibility:
- Runtime is responsible for generating export data.
generator-sdkis responsible for downloading to local, opening in Studio, and combining with billing.- The host decides when to trigger the export.
Host Integration Example
Typical integration of left-side canvas + right-side partial parameters in a template page:
const runtime = window.__GENERATOR_RUNTIME__
const sdk = GeneratorSDK.init({ appKey: 'your_app_key', env: 'prod' })
const importedTemplate = sdk.template.parse(templateText)
const snapshot = sdk.template.toRuntimeSnapshot(importedTemplate)
await runtime.mount({
mode: 'embed',
target: 'canvas',
container: canvasContainer,
state: snapshot.state,
})
const panelSchema = runtime.getPanelSchema({
panelFilter: {
...snapshot.panelFilter,
},
})
await runtime.mount({
mode: 'embed',
target: 'panel',
container: panelContainer,
state: snapshot.state,
panelFilter: snapshot.panelFilter,
})Relationship with Old Bridge
Existing generators commonly use window.__GENERATOR_BRIDGE__. V1 allows compatibility via adapters, but it is not recommended for new generators to implement only the bridge.
Recommended mapping:
getSnapshot()->getState()applySnapshot()->setState()getExportData()->export()_sync()->subscribe()+state-change
If only the adapter mapping from bridge to runtime is completed, it is recommended to describe it as "Compatibility Refactoring Complete" or "Phase 1/2 Complete" in external reports. Do not describe it as "Standard Generator Refactoring Complete."
V1 Non-Goals
The following are not goals of V1:
- A universal JSON canvas rendering engine.
- Forcing all generators to share the same layer schema.
- All parameter controls being rendered uniformly by the platform.
- One-time migration of all old generators.
V1 only defines a minimal protocol that can be consumed by Skills, MCP, Starters, and template pages.
Recommended Project Structure
New generators are recommended to be organized as follows:
src/
core/
state.ts
renderer.ts
panel-schema.ts
export.ts
runtime/
index.ts
mount-canvas.ts
mount-panel.ts
pages/
full.ts
embed.ts
platform/
sdk.tsOne-Sentence Conclusion
The essence of the Generator Runtime Contract is:
Standardizing how generators are embedded and run across different hosts, rather than transforming all generators into the same rendering engine.
A final note for existing generator refactoring:
The runtime contract is important, but it is only one part of standardization refactoring and should not replace the final completion gates.