@widgentic/designer ships five designers — widget, theme, schema, action and settings — each as a function taking a container and each as an opt-in custom element. They run in the browser, depend only on @widgentic/core, and perform no network I/O: every edit re-runs widgentic’s validators and re-renders a live preview through the real pipeline; persistence is yours.
The widget designer
createDesigner(container, options?) accepts:
The handle is
{ getDraft, getDiagnostics, loadWidget, checkWidget, loadTheme, setReadOnly, subscribe, dispose }. getDraft() returns a copy of the current draft; getDiagnostics() returns its diagnostics and checkWidget(definition) checks a definition as a load would, without touching the draft, both resolved against the schemas and actions you supplied; loadWidget(definition) and loadTheme(theme) validate first and return { ok: true } or { ok: false, errors } without touching the draft on failure; loadTheme(tokens) layers its tokens over the preview theme the person chose, which stays selected with its styles, so loadTheme({}) returns to it; loadWidget keeps the preview theme and refuses a kind Save would refuse only when the load changes the kind; subscribe(listener) calls listener(draft, diagnostics) on every change and returns an unsubscribe function; dispose() removes the UI. Read-only mode — readOnly: true or setReadOnly(true) — leaves editing surfaces visible but inert while the preview, its theme selector and Export keep working: it restricts editing, not looking.
Theme, schema, action and settings designers
-
createThemeDesigneredits a named entry{ name, label?, description?, tokens, styles? }, its shared styles in the widget designer’s own Styles panel. Itswidgetsoption adds custom widget definitions to the preview-kind selector beside the built-ins, so a theme is judged against the widgets it will dress; invalid definitions are skipped.initialThemeopens an existing entry. -
createSchemaDesigneredits a shared schema entry{ name, label?, description?, schema };initialSchemaopens one. -
createActionDesigneredits a stored action{ name, label?, description?, definition };initialActionopens one.schemasoffers shared schemas as copy-from sources for the input and output schemas, andsecretNamesoffers names for header and query values. A Test control appears only when you passtestCall(definition, args), which must run through your server’s execution path — the library performs no network I/O itself. -
createSettingsDesigneredits the principal’s settings record. Its Default themes subsection offers, per mode (light hosts, dark hosts), the built-in and every entry you pass inthemes;getSettings()returns{ defaultThemes: { light?, dark? } }, built-in choices omitted, with any section it does not edit passed through untouched. Save it withPUT /settingson the authoring surface. A name you did not pass inthemesshows as unavailable rather than disappearing. Its Resource domains subsection lists the domains the principal’s frames may load images from directly. Each entry is checked as it is typed by the same rule the store applies (parseResourceDomainfrom@widgentic/core). A valid entry is recorded in its canonicalhttps://form; an invalid one is kept as typed and marked, so the save’sINVALID_DOMAINnames it. Adding stops atRESOURCE_DOMAIN_LIMIT.getSettings()carriesresourceDomainsonce the record had it or the person edited the list, so a save clears an emptied list.
header { border-bottom: … } would dress a widget’s <header> that no MCP Apps host dresses, so fence them out of the preview:
:where() keeps the rule’s weight unchanged. Rules on your own classes need no fence.
All five accept appearance, chrome and readOnly. The widget designer only selects among the themes, schemas and actions you pass; authoring each belongs to its own designer, so a host ships only what it embeds.
Custom elements
Registration happens only through the explicit calls — importing the module has no registry side effects:widgentic-change CustomEvents whose detail carries { draft, diagnostics }, { theme }, { schema }, { action } or { settings }. Two attributes are read when the element connects: appearance (light or dark) and chrome (the token map as JSON). The wrapped handle is available as the element’s designer property.
Without a bundler, load the single-file bundle and use the elements directly:
Theming the chrome
chrome is available from @widgentic/designer 0.2.0. From 0.3.0 the built-in defaults are the widgentic palette, and CHROME_DEFAULTS and chromeCss are exported.--wgd-* custom properties, the chrome tokens (exported as CHROME_TOKENS). The widget preview inside them is not chrome: it follows the selected theme’s --wg-* tokens from @widgentic/core/theming and is unaffected by chrome.
By default the designers wear the widgentic palette, light and dark — the same colours the product uses, so an embedded designer looks like widgentic without you configuring anything. Every colour sits on the logo mark’s hue, and each one is contrast-checked in both schemes: text pairs at WCAG AA (4.5:1), and the borders that identify an input, a focused tag or a banner at 3:1. Typography is deliberately not branded — font, font-mono and the three sizes stay system values, because a library must never default to a webfont it cannot serve.
appearance picks which scheme’s defaults the chrome follows. chrome layers your values on top of them, applied inline on the designer root, so they win over the light and dark blocks:
Match your page to the designers
CHROME_DEFAULTS is not a description of the defaults — it is them; the injected stylesheet is generated from it. Paint your own surfaces from the same object and the two can never drift — and chromeReferences() is the return trip: the full chrome map of var() references under the same prefix, so the designers follow whatever your page declares. Together they are the full-takeover recipe:
chromeCss(palette, options?) takes prefix (default --wgd), selector (default :root), darkMediaSelector and an optional darkSelector for a toggle you control. It returns a string; nothing is injected for you.
Use your own palette instead
There is one default palette and no second one to fall back to — a host that wants a different look passes its own values, andchromeCss will render them for the surrounding page just the same:
chrome-contrast.test.ts if you want the same gate.
Rules: values are CSS values; CSS-wide keywords (inherit, initial, unset, revert) are ignored — on a custom property they act on the token itself, not on what reads it — so to follow your typeface pass its variable with a fallback stack (font: "var(--app-font, system-ui, sans-serif)"). Unknown tokens, non-string values and unparseable attribute JSON are ignored, never thrown. With no chrome, you get the widgentic palette.
Helpers
The entry also exports the pieces the designers are built from —importWidgetJson, exportWidgetJson, exportThemeJson, toTypeScriptModule, and checkThemeEntry, checkSchemaEntry, checkActionEntry, which validate an untrusted entry and return error strings instead of throwing. The full list is in the designer export index.