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

Published

Unified generator shell based on Web Components

Downloads

2,107

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 stylesheet into its Shadow DOM in a non-blocking way, and auto-loads the default atomm-ui CDN script when window.AtommUI is absent. The shell now starts a single background CSS fetch and patches the same CSS text into both the Shadow DOM and document.head, so CSS loading no longer blocks the initial shell render or duplicates the old <link> + fetch path. The host page no longer needs to add Vue or Atomm UI scripts just for the shell. When invitation, template publish, or credits top-up flows are enabled, the shell also starts an async background preload for atomm-pro.css, while keeping the atomm-pro JavaScript runtime lazy until the user actually opens one of those flows.

Usage

<generator-workbench id="workbench"></generator-workbench>
import {
  createAtommProHostI18nConfig,
  createGlowSensorsReporter,
} 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',
  readyPolicy: 'runtime-ready',
  templateEnabled: true,
  isAdminPublishTemplate: false,
  invitationEnabled: true,
  atommProEnv: 'test',
  atommProDomain: 'atomm',
  ...createAtommProHostI18nConfig(),
  exportEnabled: true,
  studioEnabled: true,
  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 ?? ''),
        }
      },
    }),
  },
  cloudEnabled: true,
  historyEnabled: true,
  autoSaveEnabled: true,
  autoSaveDebounceMs: 1200,
  getCloudSaveOptions: ({ state }) => ({
    title: 'Draft',
    snapshot: state,
  }),
}

await workbench.mount()

Admin-gated Template Publish

If you want only community admins to see the Publish as Template entry, enable:

workbench.config = {
  title: 'My Generator',
  templateEnabled: true,
  isAdminPublishTemplate: true,
}

Behavior:

  • When isAdminPublishTemplate is true, the publish entry is shown only when sdk.auth.getStatus().userInfo?.isCommunityAdmin === true.
  • The shell automatically reacts to auth state changes (login, logout, account switch via auth.onChange) and updates the publish entry visibility.
  • Existing projects are unaffected because the default is false.

Analytics

generator-workbench now enables a Glow-compatible reporter by default for both Download and Open in Studio.

If you do not provide config.analytics.reporter, the workbench automatically falls back to the built-in createGlowSensorsReporter(). Provide analytics.reporter only when you want to override the default mapping or sensor targets.

import { createGlowSensorsReporter } from '@atomm-developer/generator-workbench'

workbench.config = {
  title: 'My Generator',
  analytics: {
    reporter: createGlowSensorsReporter({
      sceneName: 'Atomm',
      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 ?? ''),
        }
      },
    }),
  },
}

Reporter behavior:

  • Event name: Generator_export
  • click_position: download or openinstudio
  • Built-in common fields: scene_name, item_type
  • item_type defaults to sdk.getAppKey()
  • Dispatch targets: window.dataLayer for GA4 and window.sensors.track(...) for Sensors Analytics

Template publish click:

  • 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, the reporter can work without a custom resolver. Otherwise, provide resolveExportPayload(...) to map your runtime fields into the Glow schema.

If the host wants to remove the page-level loading state as soon as the shell chrome is visible, switch to:

workbench.config = {
  title: 'My Generator',
  mode: 'shell',
  readyPolicy: 'shell-ready',
}

Lifecycle semantics:

  • readyPolicy: 'runtime-ready' keeps the existing behavior. mount() resolves after runtime mount completes and workbench-ready fires.
  • readyPolicy: 'shell-ready' makes mount() resolve right after the shell UI is rendered. The workbench then continues mounting the runtime in the background.

The element now dispatches these lifecycle events:

  • workbench-shell-ready: the Shadow DOM shell and workspace host are visible
  • workbench-runtime-ready: the runtime mount has completed
  • workbench-ready: compatibility event emitted together with workbench-runtime-ready

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 passes the token to sdk.auth.syncToken(token), allowing the SDK to persist it in the shared-domain utoken cookie and refresh the login state.

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() call sdk.billing.consume() before the actual export action when billing is enabled. If it returns isBlacklisted = true, the shell blocks Download / Open in Studio and shows Your account has been suspended due to security concerns.
  • On the credits path, the shell still calls sdk.billing.refreshCredits() after the export succeeds.

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 but the route contains ?templateId=<id>, the background bootstrap requests /community/v1/web/making/:id, logs the full response plus data.generatorInfo.info, and restores that info object into the runtime 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

For the route template request, the workbench resolves the API base URL from ?env=dev|test|prod first, otherwise it falls back to config.atommProEnv (dev -> dev, test / test_us -> test, everything else -> prod).

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 builds the publish payload from runtime state, template JSON, template meta, cover data, and origin image data, then opens the shared @atomm/atomm-pro PublishTemplateModal.
  • On the first Publish as Template open, the shell caches the resolved generatorImage and generatorTag in memory; if that first generatorImage is uploaded to OSS, the shell also caches the returned URL and reuses that URL for later opens in the same workbench instance.
  • Reopening the shared publish modal preserves the existing form draft by default; generator-workbench no longer resets the modal automatically before every reopen.
  • If config.style is provided, the shell forwards it as initialData.style in PublishTemplateModal.open(...).
  • Runtime can still override publish media through template_publish_media_change, and the shell normalizes generatorInfo to { appKey, generatorCode, generatorName, info, template, cover, originImageUrl }.
  • Before the final create/update template request is sent, any local image data still present in top-level cover or generatorInfo.cover is uploaded to OSS and replaced with the returned URL.

Local Preview

pnpm --dir generator-workbench dev

This starts the basic example in development mode with:

  • local built generator-workbench
  • local source GeneratorSDK
  • GeneratorSDK.init({ env: 'dev' })
  • atommProEnv = 'dev'
  • cloud/history UI enabled by default in the top bar

Then open http://127.0.0.1:5173/examples/basic/?env=dev.

For a local test environment preview:

pnpm --dir generator-workbench example:test

This starts the same local example with:

  • local built generator-workbench
  • local source GeneratorSDK
  • GeneratorSDK.init({ env: 'test' })
  • atommProEnv = 'test_us'
  • cloud/history UI enabled by default in the top bar

Then open http://127.0.0.1:5173/examples/basic/?env=test.

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
  • GeneratorSDK.init({ env: 'prod' })
  • atommProEnv = 'prod'
  • cloud/history UI enabled by default in the top bar

Then open http://127.0.0.1:5173/examples/basic/?env=prod.

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.