Generator Workbench
generator-workbench is the official host shell for standard generators. It sits above generator-sdk and the runtime contract: the SDK provides platform capabilities, the runtime provides business rendering and state, and the workbench provides the unified shell UI that hosts both.
If you want one dedicated page for all generator-workbench <-> runtime communication rules, read Workbench Runtime Communication.
Core Concepts
Before reading this page, the following terms are used throughout. If any of them are unfamiliar, read Architecture and Runtime Contract first.
Generator Runtime — the generator application written by you. It owns canvas rendering, business state, the parameter panel, and export data. It is not the workbench. The workbench wraps it.
Official Host Shell (generator-workbench) — this package. It owns all platform-facing UI: the top bar, login/user entry, export actions, credits badge, billing, cloud save controls, and template publishing. You do not re-implement any of these in your runtime.
Full mode — the complete editor layout: top bar + full workspace area (or right-side parameter panel in full shell mode). The workbench renders the surrounding chrome; your runtime fills the interior.
Embed mode — the generator is embedded inside a template page via ?mode=embed. The workbench hides platform chrome and keeps only the canvas, and optionally a filtered parameter panel, visible to the outer page.
Generator SDK — @atomm-developer/generator-sdk. Connects your runtime to platform APIs: auth, billing, credits, cloud save, history, and export to Studio. Initialized with your appKey.
What It Owns
- Top bar title and branding
- Guest avatar / login / avatar / logout entry
- Credits badge in the top bar
- Optional invite action powered by
@atomm/atomm-proInvitationModal - Template import entry and template publish modal
- Export and Open in Studio entry with billing consumption
- Optional shell actions for cloud save and history restore/delete
- Optional runtime-driven auto-save orchestration when the host enables it
- Runtime mounting into either a free workspace host or split canvas / panel hosts
- A consistent standard layout for new generators
Shell Modes
generator-workbench supports three host shell modes:
shell: keep the top bar, renderid="sidebar-footer"as a fixed bottom-right floating export entry, and mount the runtime into a free workspace host so the generator owns the full internal layout.full: render both the top bar and the classic right sidebar layout, and keepid="sidebar-footer"as the footer export area.template: hide the top bar, keepid="sidebar-footer", and let the outer page own branding and login entry.
These host shell modes are different from the route capability mode. If the page URL contains ?mode=embed, the workbench keeps the current shell layout but force-disables these shell-owned SDK integrations:
- cloud save
- history
- credits badge and export credits hint
- billing-backed export consumption
- invitation / earn credits entry
This is mainly for main-site iframe embedding, where the host wants a lighter shell surface while still reusing the same runtime mount path. If the route omits mode or uses mode=full, the existing behavior stays unchanged.
In this embed route, the shell also hides the top bar and #sidebar-footer export area. The related workbench capabilities remain available to the host and bridge, but the iframe no longer shows those shell chrome blocks.
When ?mode=embed is active, the workbench also starts an iframe bridge compatible with the main-site generator protocol:
- outgoing ready event:
generator_pageLoaded - incoming commands:
generator_loadTemplateData,generator_setGeneratorData,generator_getTemplateData,generator_getGeneratorData,generator_getFile - outgoing responses:
generator_toTemplateLoaded,generator_toTemplateData,generator_toGeneratorData,generator_toFile,generator_toFileError,generator_toTemplateError,generator_toSelectTemplate
The default bridge behavior is:
- apply incoming templates with
sdk.template.applyToRuntime(...) - restore
generator_setGeneratorData.data.infothroughruntime.setState(...) - build
{ template, info, cover, originImageUrl }from the current runtime state when the host asks for template data - resolve file export data from
config.embedBridge.getExportData(...)first, then fall back to the SDK export provider registered throughsdk.export.register(...)
Recommended host handshake:
- treat
generator_pageLoadedas the only bridge-ready signal because it is emitted only after the iframe has attached itsmessagelistener - do not use
iframe.onloadas the business-ready event for template/state bridge commands - queue
generator_loadTemplateData/generator_setGeneratorDataon the host untilgenerator_pageLoadedarrives, then flush the pending payloads - prefer
generator_loadTemplateDatafor template bootstrap because it receives the explicitgenerator_toTemplateLoadedresponse - keep
generator_setGeneratorDatafor runtime snapshot restore; it does not emit a dedicated success ack event - if
generator_setGeneratorDatafails, the iframe reportsgenerator_toTemplateErrorwithaction: 'setGeneratorData'
Runtime Communication Overview
Keep the main integration surface small:
- runtime reads route capability through
mount({ routeMode }) - runtime reports business events through
runtime.subscribe(listener) - workbench or host sends reverse commands through
workbench.dispatchRuntimeCommand(...) - the runtime receives those commands through
runtime.dispatchWorkbenchCommand?(...)
Minimal route-capability example:
await runtime.mount({
mode: 'full',
routeMode: 'embed',
target: 'full',
container,
})Minimal reverse-command example:
await workbench.dispatchRuntimeCommand({
type: 'open-login',
data: { source: 'topbar' },
})Current built-in communication behavior includes:
state-change-> auto-save orchestrationparams_change-> DOM eventruntime-params-changeselect_template-> DOM eventruntime-select-templateselect_templatein?mode=embed-> parent bridge eventgenerator_toSelectTemplate
For full event tables, payload rules, runtime examples, error-handling notes, and the public RuntimeEventRegistry / RuntimeEventChannel model, read:
If you are working on parent-page iframe integration instead of local workbench/runtime communication, read:
When you use template mode, the host shell can decide when the workbench should accept an external SDK token:
await workbench.setAuthToken(token)This passes the token to sdk.auth.syncToken(token), letting the SDK persist it into the shared-domain utoken cookie and refresh the login state.
What It Does Not Own
- Generator business state
- Custom drawing logic and rendering details
- Runtime schema design
- Platform API implementation details
- A full route-based project gallery or work manager
In other words: keep generator behavior in the runtime, keep platform capabilities in generator-sdk, and let generator-workbench render the standard shell around them.
Common Capability Profiles
When planning a generator-workbench integration, first decide which capability profile the generator actually needs. This is a documentation concept that helps scope the integration work; it is not an additional required runtime config field.
basic: login, export, Open in Studio, credits, and billing shell experience onlycloud:basicplus cloud save / restore and historytemplate: publish as template, template page embedding, customize flow, template import/export, or other template-authoring flows
Important:
- using
generator-workbenchdoes not automatically mean the generator needs thetemplateprofile - if the task only needs shell-level capabilities, do not add template publishing UI, template JSON protocol work, or customize flow by default
- if the task needs cloud save but not template publishing,
cloudis enough; do not silently upgrade the project totemplate
Demo
Integration Flow
- Initialize
GeneratorSDK - Define the custom element with
defineGeneratorWorkbench() - Pass
sdk,runtime, andconfigto<generator-workbench> - Call
mount() - Let the workbench mount either the full runtime workspace (
shell) or the split canvas / panel hosts (full/template)
If you want the host page to remove a page-level loading overlay as soon as the shell chrome is visible, set:
workbench.config = {
title: 'Frame Generator',
mode: 'shell',
readyPolicy: 'shell-ready',
}Lifecycle behavior:
readyPolicy: 'runtime-ready'is the default.mount()resolves after runtime mount completes.readyPolicy: 'shell-ready'makesmount()resolve after the shell UI is rendered, while runtime mount continues in the background.
Lifecycle events:
workbench-shell-ready: shell UI and workspace host are visibleworkbench-runtime-ready: runtime mount is completeworkbench-ready: compatibility event emitted together withworkbench-runtime-ready
Environment Configuration
Generator Workbench environment configuration has two layers:
| Config | Where | Controls |
|---|---|---|
GeneratorSDK.init({ env }) | SDK layer | All platform API base URLs (auth, cloud, history, credits, billing, export) and Passport login area (US / CN) |
workbench.config.atommProEnv | Shell layer | Workbench shell behavior, community template API routing, atomm-pro modal context |
atommProEnv is automatically derived from the injected SDK's env. When the host passes workbench.sdk = sdk, the workbench calls sdk.getEnv() and uses that as atommProEnv. This means you typically only need to set the SDK env — the shell layer stays in sync automatically.
// Recommended: set env once in the SDK, shell derives it automatically
const sdk = GeneratorSDK.init({ appKey: 'my-generator', env: 'prod_cn' })
workbench.sdk = sdk
workbench.config = {
// atommProEnv is NOT required — derived from sdk.getEnv() automatically
// ...
}If you do set atommProEnv explicitly and it differs from the SDK's env, the workbench logs a warning and uses the SDK's env as the source of truth.
SDK env — Supported Values
| Value | API Base URL | Auth Area |
|---|---|---|
prod | https://api.xtool.com | US |
prod_cn | https://api.makeblock.com | CN |
pre | https://api-pre.xtool.com | US |
test | https://api-test.xtool.com | US |
dev | https://api-dev.makeblock.com | CN |
When env is omitted, GeneratorSDK.init() defaults to prod.
workbench.config.atommProEnv — Supported Values
Accepts the same identifiers as the SDK env. This field is optional when an SDK instance is injected. When no SDK is available, it falls back to 'prod'.
China Production (prod_cn)
When sdk env is set to prod_cn (and atommProEnv is derived from it automatically), the full China production behavior is active:
- All platform API calls route to
https://api.makeblock.com - Passport login uses the CN authentication area
- Invitation entry (share / earn-credits button) — hidden automatically by the shell. Do not set
invitationEnabled: falsemanually. - Publish as Template entry — hidden automatically by the shell, regardless of
templateEnabled. - Default language — when the host page URL has no
?lang=query parameter, the workbench shell defaults tozhinstead ofen. An explicit?lang=enin the URL still overrides this default.
If your generator runtime owns its own locale bootstrap (for example a Vue i18n setup that reads ?lang=), apply the same defaulting logic there: fall back to 'zh' when the resolved env is prod_cn and no explicit ?lang= is present.
const sdk = GeneratorSDK.init({ appKey: 'my-generator', env: 'prod_cn' })
workbench.sdk = sdk
workbench.config = {
title: 'My Generator',
mode: 'shell',
// atommProEnv is derived from sdk automatically — no need to repeat it
atommProDomain: 'atomm',
...createAtommProHostI18nConfig(), // resolves 'zh' by default when env is prod_cn
}Export Analytics
generator-workbench attaches Glow-compatible analytics for Download and Open in Studio by default.
If config.analytics.reporter is omitted, the workbench falls back to the built-in createGlowSensorsReporter(). Set analytics.reporter only when you need to customize payload mapping or replace the default targets.
import { createGlowSensorsReporter } from '@atomm-developer/generator-workbench'
workbench.config = {
title: 'Frame Generator',
mode: 'shell',
analytics: {
reporter: createGlowSensorsReporter({
resolveExportPayload({ config, runtime, sdk }) {
const state = runtime.getState()
return {
content_type: config.title,
content_id: sdk.getAppKey?.() || '',
element_name: String(state.params?.vars?.kerf_offset ?? ''),
content_name: String(state.params?.vars?.material_preset ?? ''),
}
},
}),
},
}The built-in reporter emits:
- event name:
Generator_export click_position:downloadoropeninstudio- common fields:
scene_name,item_type item_typedefaults tosdk.getAppKey()- GA4 target:
window.dataLayer.push(...) - Sensors target:
window.sensors.track(...)
For the top-bar Publish as Template click, the same reporter also emits:
- event name:
publishTemplateClick - payload:
{ content_type: workbench.config.title }
If your runtime state already exposes modelTitle, modelId, params.vars.kerf_offset, and params.vars.material_preset, you can omit the custom resolver and use the default field inference.
Performance marks:
generator-workbench:mount:start: emitted whenmount()starts a new mount rungenerator-workbench:shell-ready: emitted together withworkbench-shell-readygenerator-workbench:runtime-ready: emitted together withworkbench-runtime-readygenerator-workbench:ready: emitted together with the compatibilityworkbench-ready
Host staged loading example:
const pageLoading = document.querySelector('#page-loading')
const workspaceSkeleton = document.querySelector('#workspace-skeleton')
workbench.config = {
title: 'Frame Generator',
mode: 'shell',
readyPolicy: 'shell-ready',
}
workbench.addEventListener('workbench-shell-ready', () => {
pageLoading?.remove()
workspaceSkeleton?.removeAttribute('hidden')
console.debug(
performance.getEntriesByName('generator-workbench:shell-ready').at(-1),
)
})
workbench.addEventListener('workbench-runtime-ready', () => {
workspaceSkeleton?.setAttribute('hidden', 'true')
})
await workbench.mount()Recommended host behavior:
- Remove the page-level loading overlay on
workbench-shell-ready - Keep a local workspace skeleton or spinner until
workbench-runtime-ready - If you keep the default
readyPolicy: 'runtime-ready',await mount()still waits for runtime completion for backwards compatibility - If you switch to
readyPolicy: 'shell-ready', rely on the staged events above instead of treatingawait mount()as the final interactive signal - If runtime mount fails after
shell-ready, listen toworkbench-error/config.onErrorso the host can remove the skeleton and show an error state
Billing And Credits
When the injected SDK exposes credits and billing, generator-workbench will automatically:
- show the current credits balance in the top bar after login
- render an export hint from
sdk.billing.getUsage() - hide
export-credits-hintwhenusage.isEnabled = false - switch the hint between
freeRemaining/freeTotal,creditsPerUse, and the30sfree-period countdown when billing is enabled - hide the credits token icon when free quota is still available and only show the
freeRemaining/freeTotaltext - when free quota is exhausted and the current credits balance is insufficient, open the
@atomm/atomm-procredits purchase modal before continuing - call
sdk.billing.consume()before the actual export action when billing is enabled; if it returnsisBlacklisted = true, the shell blocksDownload / Open in Studioand shows anatomm-uierror message - call
sdk.billing.refreshCredits()after a successful export on the credits path
This keeps the runtime focused on business rendering while the host shell owns the platform-side billing experience.
In practice, the standard export flow now looks like this:
- user clicks the footer export trigger
- workbench ensures the user is logged in
- workbench calls
sdk.billing.getUsage()to read the latestUsageInfo - if
usage.isEnabled = false, it hides the hint and performssdk.export.download()orsdk.export.openInStudio()directly - if
usage.isEnabled = true, it updates the hint frominFreePeriod / freeRemaining / creditsPerUse - if the user is already inside the
30sfree period, or still has free quota, the shell runs the export directly - if free quota is exhausted, the shell compares
usage.creditsPerUsewithusage.creditsBalance - when the balance is sufficient, it runs the export directly; when the balance is insufficient, it opens the
@atomm/atomm-procredits purchase modal, then re-reads usage before continuing - before the actual export action, workbench calls
sdk.billing.consume()as a billing gate - if
consume()returnsisBlacklisted = true, the shell blockssdk.export.download()/sdk.export.openInStudio()and showsYour account has been suspended due to security concerns - if
consume()succeeds normally, the shell continues with the export action - on the credits path, it additionally calls
sdk.billing.refreshCredits()after the export succeeds - after consume succeeds, the shell enters a 30-second free period, persists the countdown in
localStorage, and restores it after page refresh while keeping the hint text in sync
So the runtime still does not own platform billing logic, credits display, or export shell actions.
Cloud Save And History
When the injected SDK exposes cloud and history, generator-workbench can optionally add shell-owned project actions:
Save Draftin the top barHistoryin the top barsaveToCloud()for host-triggered save orchestrationloadHistory(),restoreHistoryItem(id), anddeleteHistoryItem(id)for shell-level history flows
These flows intentionally stay at the shell layer:
- the runtime still owns the real business state
- the workbench only reads state and writes snapshots back through
runtime.setState(...) - the host page still owns any route, workspace list, project gallery, or broader application workflow
In other words, this is an optional shell capability, not a full “project center.”
Cloud save has two different levels:
- state-only save: the shell saves the runtime
snapshotwithout a cover image; this is still a valid cloud integration - cover-enhanced save: the host or runtime also provides a current preview image through
cover
Do not treat missing cover as "cloud save is not integrated". cover is an enhancement for draft thumbnails and history visuals, not a requirement for saving the business state itself.
Before an explicit cloud or history action such as saveToCloud(), loadHistory(), restoreHistoryItem(id), or deleteHistoryItem(id), the shell checks sdk.auth.getToken(). If there is no token yet, it calls sdk.auth.login() first and only continues once that user-triggered login flow succeeds.
When cloudEnabled is active, the shell also supports a lightweight route bootstrap:
- the runtime mounts first so the main canvas and panel can render immediately
- after the runtime is mounted, the workbench evaluates the route bootstrap in the background
- if the route already contains
gidand a local token already exists, the workbench restores the cloud record throughsdk.cloud.restore(gid) - if the route already contains
gidbut the user is still logged out, the workbench defers the cloud bootstrap, shows a lightweight hint, and waits for a later explicit login or cloud/history action - after that explicit login succeeds, the workbench binds the route
gidinto the current shell session without automatically restoring server content back into the runtime - if
gidis missing but the route containstemplateId, the workbench requests/community/v1/web/making/:id, logs the full response plusdata.generatorInfo.info, and restores thatinfoobject into the runtime - if
gidis missing and a local token already exists, the workbench creates one cloud record throughsdk.cloud.save(...)and writes the returned id back into the route - if
gidis missing and the user is logged out, the workbench skips the initial cloud record creation until the user explicitly saves
For the route template request, the API base URL is resolved from ?env=dev|test|prod first. If that query is absent, the shell falls back to config.atommProEnv (dev -> dev, test / test_us -> test, everything else -> prod).
This keeps login or cloud bootstrap latency from blocking the runtime render path. More importantly, a logged-out gid route no longer forces an automatic restore that could overwrite local edits made before the user explicitly enters a cloud/history flow.
When the route contains ?mode=embed, this cloud bootstrap is skipped even if the host config enabled cloudEnabled.
This keeps the route-level record identity in the host shell without turning the workbench into a full project manager.
Invitation Modal
By default, the workbench adds an Earn Credits button to the left of the top-bar credits badge. When logged out, the workbench replaces the text Login button with a 40x40 guest avatar. To keep the invitation flow working, the host should still provide:
config.atommProEnvconfig.atommProDomain
Under the hood, the shell starts an async background preload for atomm-pro.css as soon as invite / publish / credits flows are enabled, but still keeps Pinia, Vue Router, Vue I18n, and the bundled atomm-pro JavaScript runtime lazy until the user actually opens the flow. It then mounts XtAtommProContext inside the workbench app and opens InvitationModal with the provided configKey.
Keep these constraints in mind:
- the invite entry is visible even before login; clicking it while logged out first calls
sdk.auth.login()and then opensInvitationModal - if you need to hide the invite entry, explicitly pass
config.invitationEnabled = false - if
invitationConfigKeyis omitted, the shell defaults it togenerator_${sdk.getAppKey()} - if you need a different referral/share key, explicitly pass
config.invitationConfigKey - if you need localized atomm-pro copy, either pass
config.atommProLocale/config.atommProMessagesmanually or reusecreateAtommProHostI18nConfig()from the package entry
Optional Auto-Save
If the host enables:
config.cloudEnabled = trueconfig.autoSaveEnabled = trueconfig.getCloudSaveOptions(...)
and the runtime implements subscribe(listener), the workbench can debounce runtime changes into cloud saves.
This gives AI-generated or rapidly integrated generators a standard autosave path without moving business state ownership out of the runtime.
If the runtime also includes data.cover on params_change events (for example a thumbnail data URL), the workbench caches the latest non-empty value and automatically reuses it for later sdk.cloud.save(...) calls whenever the host did not explicitly provide cover.
In practice, the cover source priority is:
config.getCloudSaveOptions(...)explicitly returnscoverconfig.getCloudCover(...)is called and returns a non-empty value, which the shell injects ascover- the runtime emits a later non-empty
params_change.data.cover, which the shell reuses as a cached thumbnail - no cover is available, so the shell still saves the current
snapshotwithout thumbnail enhancement
This is why a generator can be fully integrated for cloud save even before it implements a dedicated cover-export path.
getCloudCover
If the host already has a way to export the current canvas preview but does not want to restructure the entire getCloudSaveOptions return value, use the dedicated cover hook instead:
workbench.config = {
cloudEnabled: true,
autoSaveEnabled: true,
getCloudSaveOptions(context) {
return {
title: 'My Generator Draft',
snapshot: context.state,
// no cover here — handled by getCloudCover below
}
},
async getCloudCover(context) {
// context exposes sdk, runtime, and the current state
return await context.runtime.export({ action: 'cover', format: 'png' })
},
}The shell calls getCloudCover only when the cover field is absent from the result of getCloudSaveOptions. Old projects that already return cover inside getCloudSaveOptions are completely unaffected.
If getCloudCover throws or returns empty, the shell logs a warning and falls back to the cached thumbnail from params_change.data.cover (or saves without a cover if that is also unavailable).
For runtime-owned edits that need an explicit save payload, the runtime can dispatch runtime-cloud-save-request with sdk.cloud.save(...)-compatible detail. The workbench debounces that request for 2 seconds and injects the current route gid as options.id before saving.
Embed Adaptation Levels
When ?mode=embed is active, the workbench lowers its shell surface. But the runtime still needs to decide how much of its own UI to show. These levels give you a shared vocabulary so that skill, documentation, and validation checklists all refer to the same thing.
Start with
embed-basicby default. Only upgrade toembed-canvas-panelwhen the template page needs to render an editable parameter panel from the generator's ownPanelSchema.
embed-basic
The minimal embed integration. The workbench hides shell chrome, starts the iframe bridge, and the runtime accepts template or snapshot data. This does not require any special layout change inside the runtime; it only requires that the runtime correctly responds to state injection and does not render platform-owned chrome in its own UI.
Acceptance criteria:
?mode=embedhides the top bar and export area- The runtime can receive a template payload through the bridge and restore state through
setState()/patchState() - The runtime does not render platform-owned login, credits, or export buttons when
routeMode === 'embed'
embed-canvas-only
The runtime renders only its canvas output and nothing else. This is the expected level for template preview pages where the host owns the parameter panel or shows no editing controls at all.
Acceptance criteria:
- Everything in
embed-basic - The runtime hides its sidebar, template list, and any left-column navigation when
routeMode === 'embed' - Mobile layout: only the canvas content is visible
- The runtime mounts correctly with
target: 'canvas'and responds to container size
embed-canvas-panel
The runtime supports independent canvas and panel mounting, so a host can split them into separate DOM containers. This level is required when a template page renders a filtered parameter panel next to the canvas.
Acceptance criteria:
- Everything in
embed-basic - The runtime responds to
target: 'canvas'andtarget: 'panel'independently getPanelSchema()returns a filterable schema and the runtime respectspanelFilterpassed at mount time- When mounted with
target: 'canvas'only, the runtime does not show any panel UI
Use embed-canvas-only as the default when a generator is first introduced to template pages. Upgrade to embed-canvas-panel only when the template page needs to render an editable parameter panel from the generator's own PanelSchema.
When documenting, testing, or delivering a generator that needs template support, always state which embed level it targets. Do not use the term "embed adaptation" without specifying the level.
Template Publishing Flow
When the top bar template action is used, generator-workbench now:
- ensures the current user is logged in before opening the publish flow
- resolves the exportable
bind.pathset fromruntime.getPanelSchema(), still respectingconfig.getTemplateFieldPaths() - builds the standard template JSON through
sdk.template.build(...) - resolves the cover image from
config.embedBridge.getExportData('cover', ...)first, then falls back to the SDK export provider when available - lazy-mounts
@atomm/atomm-pro'sPublishTemplateModal - calls
PublishTemplateModal.open(...)with:generatorImage: runtimetemplate_publish_media_change.data.generatorImagefirst; if absent,originImageUrl; otherwise the resolved cover data URLgeneratorTag: the latestruntimeselect_templateeventdata.namefirst; if no such event has arrived yet, fall back toconfig.getTemplateMeta()?.generatorTaginitialData.style: fromconfig.style; use this when the host wants to bridge a runtime-resolved style mode into the publish modalinitialData.summary:config.getTemplateMeta()?.summaryinitialData.contentTags:config.getTemplateMeta()?.contentTagsinitialData.cover: runtimetemplate_publish_media_change.data.coverfirst; otherwise the resolved cover data URLinitialData.generatorInfo:{ appKey, generatorCode, generatorName, info, template, cover, originImageUrl }
- before the shared atomm-pro modal submits its create/update request, any local image data still present in top-level
coverorgeneratorInfo.coveris uploaded to OSS and replaced with the returned URL - on the first publish open, caches the resolved
generatorImage/generatorTag; if that firstgeneratorImageis uploaded to OSS, later opens reuse the cached OSS URL instead of re-uploading the original local image payload - when the user closes and reopens the shared modal, keeps the existing form draft by default instead of resetting the modal automatically
Where:
generatorInfo.generatorCodeis the same value asgeneratorInfo.appKeygeneratorInfo.generatorNamecomes fromconfig.getTemplateMeta()?.title, then falls back toconfig.titlegeneratorInfo.infois built from the latest runtime state using the sameinfoshape thatsdk.cloud.save(...)submits to/ai/v5/artimind/generator:{ version: '1.0.0', ...snapshot, originImageUrl }
Field source guide:
| Field | Current default source | Notes |
|---|---|---|
generatorImage | template_publish_media_change.data.generatorImage -> originImageUrl -> resolved cover data URL | Runtime or host may provide a more accurate current preview |
generatorTag | latest select_template event data.name -> config.getTemplateMeta()?.generatorTag | If the generator has no template-selection concept, this may stay empty |
initialData.cover | template_publish_media_change.data.cover -> resolved cover data URL | Optional enhancement, usually a preview thumbnail |
generatorInfo.generatorName | config.getTemplateMeta()?.title -> config.title | The shell already provides a safe fallback |
generatorInfo.info | latest runtime state mapped to the cloud info shape | Built by the shell from runtime state |
generatorInfo.template | sdk.template.build(...) | Required for template-authoring flows |
This means the integration work is usually about identifying the runtime-owned fields that have no universal fallback, not about re-implementing the full publish flow from scratch.
getTemplatePublishPayload
If the generator needs to supply generatorImage, generatorTag, cover, or originImageUrl but the default resolution chain does not reach them automatically, use the dedicated aggregated hook:
workbench.config = {
templateEnabled: true,
isAdminPublishTemplate: false,
async getTemplatePublishPayload(context) {
// context exposes sdk, runtime, and the workbench element
const coverDataUrl = await context.runtime.export({ action: 'cover', format: 'png' })
const state = context.runtime.getState()
return {
// current canvas preview used as the template preview image
generatorImage: coverDataUrl,
// name of the currently selected template style; omit if the generator has no template list
generatorTag: state.selectedTemplateName ?? '',
// thumbnail shown in the publish modal
cover: coverDataUrl,
// URL of the original source asset that can restore the template's origin media
originImageUrl: state.originImageUrl ?? '',
}
},
}isAdminPublishTemplate
If template publishing should be available only to community admins, enable:
workbench.config = {
templateEnabled: true,
isAdminPublishTemplate: true,
}When enabled, the shell shows the Publish as Template entry only when auth.userInfo.isCommunityAdmin === true. The visibility is updated automatically on auth state changes (login/logout/account switch) through the built-in auth subscription. If omitted (default false), behavior remains exactly the same as existing integrations.
The shell uses the returned object as an override layer on top of the existing resolution chain. Only the fields you explicitly return are overridden; any field you leave out continues to fall back to the shell's built-in logic. Old projects that do not configure this hook are completely unaffected.
If getTemplatePublishPayload throws, the shell logs a warning and proceeds with the original default resolution for all fields.
This keeps template field discovery and standard template serialization inside the shell, while delegating the final business publish UX to the shared atomm-pro modal.
Debug Configuration
During development, the shell can log its internal payloads to the console so you can verify the integration without opening DevTools network tabs or adding breakpoints inside the workbench source.
workbench.config = {
debug: {
// print the full cloud save payload each time autosave or an explicit save runs
logCloudPayload: true,
// print the full template publish payload when the publish modal opens
logTemplatePublishPayload: true,
// warn when required publish fields such as generatorImage or template are missing or empty
warnOnMissingTemplateFields: true,
},
}All options default to false. The debug config has no effect on production behavior; it only adds console.debug and console.warn output. Remove or set all options to false before shipping.
Typical workflow:
- Enable
logCloudPayloadwhen verifying that the cloud savesnapshotandcovermatch what you expect. - Enable
logTemplatePublishPayloadwhen verifying thatgeneratorImage,generatorTag, andgeneratorInfo.templateare populated correctly before the publish modal opens. - Enable
warnOnMissingTemplateFieldsduring initial integration to catch empty or missing fields that will silently degrade the publish preview.
Recommended Usage
- Use it for new standard generators that need a unified platform shell.
- Prefer
shellmode for AI-generated or rapid-integration generators where the runtime should own the entire workspace layout. - Combine it with
starter-html-runtimeorstarter-vue-runtimeso the runtime stays focused on business rendering. - Use
fullonly when you intentionally want the built-in right sidebar split layout. - Keep manual page layout customization outside the workbench only when you intentionally need a non-standard shell.
- Treat complex iframe-host setups, legacy bridge compatibility, and DOM-selector-based media extraction as advanced integration patterns rather than the default path for every generator.
Installation Note
CDN loading behavior, Vue auto-loading, and atomm-ui script/style notes now live in Installation so those details do not need to be repeated here.