npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@atomm-developer/generator-workbench

v0.1.14

Published

Unified generator shell based on Web Components

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-pro InvitationModal
  • 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-workbench

CDN

<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 invitationConfigKey is omitted, the shell defaults it to generator_${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 / atommProMessages manually, or reuse createAtommProHostI18nConfig() as a host-side starter template.

Shell Modes

  • mode: 'shell' keeps the top bar, moves #sidebar-footer to 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_loadTemplateData applies the incoming template through sdk.template.applyToRuntime(...)
  • generator_setGeneratorData restores data.info back into runtime.setState(...)
  • generator_getTemplateData builds a fresh template from the current runtime state and returns { template, info, cover, originImageUrl }
  • generator_getFile reads export data from config.embedBridge.getExportData(...) first, then falls back to the SDK export provider registered through sdk.export.register(...)

Host-side timing rules:

  • treat generator_pageLoaded as the only bridge-ready signal; it is emitted only after the iframe has attached its message listener
  • do not use iframe.onload as the business-ready signal for bridge commands
  • queue generator_loadTemplateData / generator_setGeneratorData on the host until generator_pageLoaded arrives, then flush them
  • prefer generator_loadTemplateData for template bootstrap because it returns generator_toTemplateLoaded
  • keep generator_setGeneratorData for runtime snapshot restore only; it does not emit a dedicated success ack event
  • if generator_setGeneratorData fails, the iframe reports generator_toTemplateError with action: '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 handlers
  • RuntimeEventChannel: connect runtime.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 event runtime-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) for runtime -> workbench
  • runtime.dispatchWorkbenchCommand(command) for workbench -> 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 default postMessage(..., '*')
  • validateOrigin(origin, event): filter incoming parent messages
  • getExportData(action, context): provide bridge export data explicitly
  • getOriginImageUrl(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.credits and sdk.billing are available, the top bar shows the current credits balance after login.
  • The export trigger reads sdk.billing.getUsage() and hides the hint completely when usage.isEnabled = false.
  • When billing is enabled, the hint switches between freeRemaining/freeTotal, creditsPerUse, and the 30s free-period countdown.
  • When free quota remains, the export hint hides the credits token icon and only shows the remaining/total text.
  • When free quota is exhausted, exportSvg() and openInStudio() compare usage.creditsPerUse with usage.creditsBalance; if balance is insufficient, they open the @atomm/atomm-pro credits purchase modal before retrying the export path.
  • exportSvg() and openInStudio() export first, then call sdk.billing.consume() on success; the credits path also calls sdk.billing.refreshCredits().

Cloud Save And History

When the injected SDK exposes cloud and history, generator-workbench can optionally provide:

  • a top bar Save Draft action
  • a top bar History action
  • saveToCloud() for host-triggered save
  • loadHistory() for host-triggered history query
  • restoreHistoryItem(id) for restoring a saved snapshot into the runtime
  • deleteHistoryItem(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 calls sdk.cloud.restore(id) and writes the restored snapshot back through runtime.setState(...)
  • if gid is missing, the background bootstrap calls sdk.cloud.save(...) once and writes the returned id back into the current route as gid

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-start
  • cloud-saved
  • runtime-cloud-save-request
  • history-load-start
  • history-loaded
  • history-restore-start
  • history-restored
  • history-delete-start
  • history-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-workbench reads runtime.getPanelSchema(), renders the generator's own field groups in a modal, and preselects exportable bind.path fields.
  • 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 dev

This 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 prod

This 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.