@atomm-developer/generator-workbench
v0.1.14
Published
Unified generator shell based on Web Components
Maintainers
Readme
Generator Workbench
generator-workbench is the unified host shell for generators built on top of generator-sdk and the generator runtime contract.
V1 provides:
- Web Component shell
- guest avatar / login / avatar / logout entry
- credits badge and export credit hint
- built-in invitation action backed by
@atomm/atomm-proInvitationModal - local template import entry and template publish modal
- billing-backed export actions
- optional cloud save and history shell actions
- optional runtime-driven auto-save orchestration
- runtime mounting for either a free workspace host or split canvas / panel hosts
Install
pnpm add @atomm-developer/generator-sdk
pnpm add @atomm-developer/generator-workbenchCDN
<script src="https://static-res.atomm.com/scripts/js/generator-sdk/index.umd.js"></script>
<script src="https://static-res.atomm.com/scripts/js/generator-sdk/generator-workbench/index.umd.js"></script>generator-workbench auto-loads Vue 3 when window.Vue is absent, injects the default atomm-ui CDN CSS into its Shadow DOM, and auto-loads the default atomm-ui CDN script when window.AtommUI is absent. The host page no longer needs to add Vue or Atomm UI scripts just for the shell. When invitation or credits top-up flows are needed, the shell also lazy-loads the bundled atomm-pro assets on demand.
Usage
<generator-workbench id="workbench"></generator-workbench>import { createAtommProHostI18nConfig } from '@atomm-developer/generator-workbench'
GeneratorWorkbench.defineGeneratorWorkbench()
const workbench = document.getElementById('workbench')
workbench.sdk = sdk
workbench.runtime = runtime
workbench.config = {
title: 'My Generator',
mode: 'shell',
templateEnabled: true,
invitationEnabled: true,
atommProEnv: 'test',
atommProDomain: 'atomm',
...createAtommProHostI18nConfig(),
exportEnabled: true,
studioEnabled: true,
cloudEnabled: true,
historyEnabled: true,
autoSaveEnabled: true,
autoSaveDebounceMs: 1200,
getCloudSaveOptions: ({ state }) => ({
title: 'Draft',
snapshot: state,
}),
}
await workbench.mount()If you need to override the default CDN addresses, pass:
workbench.config = {
title: 'My Generator',
vueScriptUrl: 'https://your-cdn/vue.global.prod.js',
vueRouterScriptUrl: 'https://your-cdn/vue-router.global.prod.js',
piniaScriptUrl: 'https://your-cdn/pinia.iife.prod.js',
vueI18nScriptUrl: 'https://your-cdn/vue-i18n.global.prod.js',
atommUiCssUrl: 'https://your-cdn/atomm-ui.css',
atommUiScriptUrl: 'https://your-cdn/atomm-ui.js',
atommProCssUrl: 'https://your-cdn/atomm-pro.css',
atommProScriptUrl: 'https://your-cdn/atomm-pro.js',
}Invitation Modal
By default, the shell adds an Earn Credits button to the left of the top-bar credits badge. When the user is logged out, the top bar also shows a 40x40 guest avatar instead of a text Login button.
import { createAtommProHostI18nConfig } from '@atomm-developer/generator-workbench'
workbench.config = {
title: 'My Generator',
atommProEnv: 'prod',
atommProDomain: 'atomm',
...createAtommProHostI18nConfig(),
}generator-workbench will then auto-load Pinia, Vue Router, Vue I18n, and the bundled atomm-pro browser assets, mount XtAtommProContext inside the shell, and call InvitationModal.open({ configKey }) when the user clicks the button. If the user is still logged out, the shell first calls sdk.auth.login() and opens the invitation/share modal right after login completes.
Notes:
- If you need to hide the invite entry, explicitly pass
invitationEnabled: false. - If
invitationConfigKeyis omitted, the shell defaults it togenerator_${sdk.getAppKey()}. - If you still need a custom referral/share key, you can explicitly pass
invitationConfigKey. - If you need localized invitation copy, you can either pass
atommProLocale/atommProMessagesmanually, or reusecreateAtommProHostI18nConfig()as a host-side starter template.
Shell Modes
mode: 'shell'keeps the top bar, moves#sidebar-footerto a fixed bottom-right floating export entry, and mounts the runtime into a free workspace host so the generator owns the full internal layout.mode: 'full'keeps the classic shell with the top bar plus the built-in right sidebar layout for separate canvas / panel mounting.mode: 'template'hides the top bar, keeps#sidebar-footer, and is useful when the host page already owns branding and login UI.
The shell layout mode above is different from the route capability mode. When the page URL contains ?mode=embed, generator-workbench keeps the current layout mode but force-disables shell integrations for:
- cloud save
- history
- credits badge / export credits hint
- billing-backed export consumption
- invitation / earn credits entry
This is useful when the host site embeds a generator inside an iframe and wants a lighter shell surface. If the route omits mode or uses mode=full, the existing shell behavior stays unchanged.
In this embed route, the shell also hides the top bar and #sidebar-footer export container. The underlying workbench methods and bridge actions still exist, but the shell chrome is no longer visible inside the iframe.
When ?mode=embed is active, the workbench also boots an iframe bridge compatible with the main-site generator protocol:
- outgoing ready signal:
generator_pageLoaded - incoming host commands:
generator_loadTemplateData,generator_setGeneratorData,generator_getTemplateData,generator_getGeneratorData,generator_getFile - outgoing result events:
generator_toTemplateLoaded,generator_toTemplateData,generator_toGeneratorData,generator_toFile,generator_toFileError,generator_toTemplateError,generator_toSelectTemplate
Default bridge mappings:
generator_loadTemplateDataapplies the incoming template throughsdk.template.applyToRuntime(...)generator_setGeneratorDatarestoresdata.infoback intoruntime.setState(...)generator_getTemplateDatabuilds a fresh template from the current runtime state and returns{ template, info, cover, originImageUrl }generator_getFilereads export data fromconfig.embedBridge.getExportData(...)first, then falls back to the SDK export provider registered throughsdk.export.register(...)
Host-side timing rules:
- treat
generator_pageLoadedas the only bridge-ready signal; it is emitted only after the iframe has attached itsmessagelistener - do not use
iframe.onloadas the business-ready signal for bridge commands - queue
generator_loadTemplateData/generator_setGeneratorDataon the host untilgenerator_pageLoadedarrives, then flush them - prefer
generator_loadTemplateDatafor template bootstrap because it returnsgenerator_toTemplateLoaded - keep
generator_setGeneratorDatafor runtime snapshot restore only; it does not emit a dedicated success ack event - if
generator_setGeneratorDatafails, the iframe reportsgenerator_toTemplateErrorwithaction: 'setGeneratorData'
Runtime Route Mode And Events
runtime.mount({ mode }) only describes how the runtime should mount into the current container. It is not enough to represent the real page route capability mode.
generator-workbench now passes an additional routeMode field into every runtime mount call:
await runtime.mount({
mode: 'full',
routeMode: 'embed',
target: 'full',
container,
})This matters in shell mode because the runtime can still be mounted as mode: 'full' while the actual page route is ?mode=embed.
If the runtime needs to notify the host that a parameter field changed, emit:
emit({
type: 'params_change',
data: {
field: 'width',
value: 120,
params: {
width: 120,
height: 128,
},
},
})generator-workbench receives this event and dispatches the DOM custom event runtime-params-change.
If the runtime needs to notify the host that a template card was selected, emit:
emit({
type: 'select_template',
data: {
name: 'Spring Sale',
category: 'marketing',
},
})When the current route is ?mode=embed, generator-workbench forwards this runtime event to the parent page through the iframe bridge as generator_toSelectTemplate.
Runtime Event Channel
generator-workbench now standardizes runtime communication behind a dedicated channel layer:
RuntimeEventRegistry: register runtime event handlers and workbench command handlersRuntimeEventChannel: connectruntime.subscribe(...), route runtime events, and forward workbench commands back to the runtime
The workbench exports both classes as public API, and the built-in shell behavior already uses them internally for:
- runtime
state-change-> auto-save orchestration - runtime
params_change-> DOM eventruntime-params-change - runtime
select_template-> DOM event + iframe bridge forwarding
If the host needs to send a command to the runtime, call:
await workbench.dispatchRuntimeCommand({
type: 'open-login',
data: {
source: 'topbar',
},
})dispatchRuntimeCommand() is the element-facing helper. Internally it forwards to the channel method dispatchWorkbenchCommand(...), which finally calls runtime.dispatchWorkbenchCommand(...).
On the runtime side, implement the optional receiver:
runtime.dispatchWorkbenchCommand = async (command) => {
if (command.type === 'open-login') {
openLoginPanel(command.data)
}
}The recommended direction split is:
runtime.subscribe(listener)forruntime -> workbenchruntime.dispatchWorkbenchCommand(command)forworkbench -> runtime
Error handling note:
- runtime event handler failures and command handler failures are reported through
config.onError/workbench-error dispatchRuntimeCommand()resolves after the dispatch attempt; it does not currently reject on handler errors
If the host needs stricter control, pass config.embedBridge:
targetOrigin: override the defaultpostMessage(..., '*')validateOrigin(origin, event): filter incoming parent messagesgetExportData(action, context): provide bridge export data explicitlygetOriginImageUrl(context): return the origin image URL stored by the host/runtime
In template mode, the host shell can pass an external token into the workbench:
await workbench.setAuthToken(token)This writes the token to localStorage using the SDK app key and then syncs the login state through sdk.auth.syncToken(token).
Billing Behavior
- When
sdk.creditsandsdk.billingare available, the top bar shows the current credits balance after login. - The export trigger reads
sdk.billing.getUsage()and hides the hint completely whenusage.isEnabled = false. - When billing is enabled, the hint switches between
freeRemaining/freeTotal,creditsPerUse, and the30sfree-period countdown. - When free quota remains, the export hint hides the credits token icon and only shows the
remaining/totaltext. - When free quota is exhausted,
exportSvg()andopenInStudio()compareusage.creditsPerUsewithusage.creditsBalance; if balance is insufficient, they open the@atomm/atomm-procredits purchase modal before retrying the export path. exportSvg()andopenInStudio()export first, then callsdk.billing.consume()on success; the credits path also callssdk.billing.refreshCredits().
Cloud Save And History
When the injected SDK exposes cloud and history, generator-workbench can optionally provide:
- a top bar
Save Draftaction - a top bar
Historyaction saveToCloud()for host-triggered saveloadHistory()for host-triggered history queryrestoreHistoryItem(id)for restoring a saved snapshot into the runtimedeleteHistoryItem(id)for deleting a history item from the shell flow
These are shell-level orchestration hooks, not a full project gallery or route-based work manager. The runtime still owns business state, and the host page still owns any larger application workflow.
Before the workbench calls sdk.cloud.save(...) or sdk.cloud.restore(...), it first checks sdk.auth.getToken(). If there is no token, it will call sdk.auth.login() and only continue the cloud action after login completes.
When cloudEnabled is on, the workbench now also supports a route-based bootstrap flow:
- the runtime mounts first so the canvas/panel can render immediately
- after the runtime is mounted, the workbench starts the route bootstrap in the background
- if the page URL contains
?gid=<id>, the background bootstrap callssdk.cloud.restore(id)and writes the restored snapshot back throughruntime.setState(...) - if
gidis missing, the background bootstrap callssdk.cloud.save(...)once and writes the returned id back into the current route asgid
This means a pending login flow or a dismissed login modal no longer blocks the runtime from rendering. The cloud bootstrap will either finish later or fail through the normal workbench error channel without blanking the runtime area.
If the page route contains ?mode=embed, this cloud bootstrap flow is skipped even when cloudEnabled was set by the host config.
Cloud/history events are also emitted as DOM custom events:
cloud-save-startcloud-savedruntime-cloud-save-requesthistory-load-starthistory-loadedhistory-restore-starthistory-restoredhistory-delete-starthistory-deleted
Auto-Save
If config.autoSaveEnabled is true and the injected runtime implements subscribe(listener), the workbench can debounce runtime changes into cloud saves:
workbench.config = {
title: 'My Generator',
cloudEnabled: true,
autoSaveEnabled: true,
autoSaveDebounceMs: 1500,
getCloudSaveOptions: ({ state }) => ({
title: 'Draft',
snapshot: state,
}),
}This is intentionally optional and only wires shell-level save orchestration. It does not replace product-level autosave policy, route sync, or record management owned by the host app.
If the runtime wants to request an explicit cloud save with custom payload, it can dispatch:
workbench.dispatchEvent(
new CustomEvent('runtime-cloud-save-request', {
detail: {
title: 'Draft',
snapshot: runtime.getState(),
},
bubbles: true,
composed: true,
}),
)The workbench will debounce this event for 2 seconds and then call sdk.cloud.save(...) with the current route gid injected as options.id.
Template Publish Behavior
- Clicking the top bar template publish action no longer downloads immediately.
generator-workbenchreadsruntime.getPanelSchema(), renders the generator's own field groups in a modal, and preselects exportablebind.pathfields.- The template JSON is downloaded only after the user confirms the selected fields.
config.getTemplateFieldPaths()still works as the default subset for the publish modal.
Local Preview
pnpm --dir generator-workbench devThis starts the basic example in development mode with:
- local built
generator-workbench - local source
GeneratorSDK - default SDK env =
test - cloud/history UI enabled by default in the top bar
Then open http://127.0.0.1:5173/examples/basic/.
To verify debounced auto-save in the same example, append ?autoSave=1.
For a CDN-style preview:
pnpm --dir generator-workbench prodThis starts the same example with:
- CDN
generator-workbench - CDN
GeneratorSDK - default SDK env =
prod - cloud/history UI enabled by default in the top bar
For the new shell-mode example, open http://127.0.0.1:5173/examples/shell/.
Both examples enable the invitation button by default. If you want to hide it for comparison, append ?invitation=0.
