sdk-restructuring.decision-log.md
May 21, 2026 · View on GitHub
Title: SDK restructuring — inversion, relocation, plugin API, config naming
Proposed by: Piotr Błaszczyk, Jan Librowski, Jakub Skibiński
Date: 22.04.2026
Status: archival. This log describes the factory-based public API (
createWorkflowBuilder({...})) that was the SDK entry point as of 22.04.2026. Superseded byrefactor/wb-root-context—createWorkflowBuilder,Editor,WorkflowBuilderConfig,WorkflowBuilderInstance(Props), anddefineNodeTemplateare all gone; the entry point is now<WorkflowBuilder.Root>. References below describe the world as of the decision date and are intentionally not rewritten — see current SDK exports inpackages/sdk/src/index.tsfor the post-refactor surface.
Context
Phase 1 of the SDK work shipped @workflowbuilder/sdk as a thin facade over apps/frontend: the SDK's src/index.ts barrel just re-exported Editor and createWorkflowBuilder from the frontend via @workflow-builder/frontend/app/* tsconfig paths and Vite aliases. That worked as a distribution shell but left a mess underneath — "frontend" was simultaneously an SPA and the canonical source of the editor component, and the SDK looked like it was what it wasn't.
The 21.04.2026 API sync settled the follow-ups needed to make the SDK what the name promises: the actual library package, with a clean consumer-facing API. This log records the decisions from that meeting and the shape we implemented on the feat/sdk-public-api branch.
Decision
Six coordinated changes, committed in order to keep the tree typechecking, linting, and building at every step.
1. Invert the source-of-truth direction
Editor source moves from apps/frontend to the SDK. All of apps/frontend/src/app/{editor.tsx, create-workflow-builder.tsx, app.tsx, features, hooks, store, components, utils} migrated into the SDK's src/ tree. The frontend becomes a thin consumer that imports @workflowbuilder/sdk like any external consumer would, plus its own demo content (node type definitions, templates, plugins).
Why: with the editor living in the SDK, the frontend no longer has a dual role. The SDK consumes its own source internally (no more round-trip through frontend re-exports), and external consumers use the exact same entry points the demo uses. No secret internal API.
How to apply: any new WB feature (store slice, diagram behaviour, JsonForms renderer, etc.) belongs under packages/sdk/src/. apps/demo/ is for demo-specific content (example node types, example templates, the bootstrap page).
2. Move SDK out of apps/
apps/sdk → packages/sdk. apps/ now holds only runnable apps (demo, docs, icons, tools); packages/ holds libraries.
Why: SDK is a library, not an app. Keeping libraries in apps/ was a left-over from when everything in the monorepo happened to be an app. Splitting makes the intent visible.
How to apply: new libraries go under packages/. Update pnpm-workspace.yaml if you add a new top-level directory pattern; the base currently globs both ./apps/* and ./packages/*.
3. Rename apps/frontend → apps/demo, delete apps/wb-demo
apps/frontend is demo-of-the-SDK after the inversion; the name says so. apps/wb-demo was the bridge we had built to validate library consumption while the editor still lived in the frontend. After step 1 the bridge is redundant, so it's gone. The package name @workflow-builder/frontend became @workflow-builder/demo.
Why: calling the demo "frontend" stopped being true when it became a consumer. Keeping it would have confused every new contributor reading the repo.
How to apply: any CI/Docker reference to dist/apps/frontend is now dist/apps/demo, and the build-docker.sh and bitbucket-pipelines.yml targets point at apps/demo/Dockerfile and @workflow-builder/demo.
4. Plugin API: factory functions (sync-only)
Plugins used to register themselves via side-effect imports — each plugin-exports.ts ran registerComponentDecorator(...) at module top level, and the app did import './plugins-bootstrap' to force those side effects. That coupled "plugins exist" to "plugins registered" with no way for the consumer to opt out of a specific plugin.
The new API accepts plugin functions through createWorkflowBuilder({ plugins: [...] }). Each plugin's plugin-exports.ts now export function plugin() that wraps its register calls. The demo composes the array explicitly in apps/demo/src/app/app.tsx. The plugins-bootstrap.ts side-effect file is deleted.
WorkflowBuilderPlugin is () => void. The factory invokes each plugin synchronously and fully before returning the WorkflowBuilder component — there is no async path, no ready promise, no two-phase init. Async work (WASM load, fetched config, feature-flag lookup) is consciously out of scope for the SDK and is the consumer's responsibility: pre-resolve the data before building the plugin function. An earlier draft of this contract had () => void | Promise<void> with the runtime doing void plugin() — that combination was a broken contract: the signature invited async, the runtime didn't await it, the plugin registered after the first render, and any thrown error vanished into an UnhandledPromiseRejection warning instead of surfacing as a real failure. Removed.
Why: zero async plugins exist in the repo today (demo, AI Studio, all of them sync). Adding a ready: Promise<void> to the factory return adds three new contract decisions (sequential vs parallel, fail-fast vs continue, plugin metadata) without a real use case to ground them. If a real async case appears, widening () => void to () => void | Promise<void> later is non-breaking — every existing plugin still type-checks. We design that API when we have a concrete use case to design against, not in the void.
How to apply: new plugins export a synchronous function plugin() that calls register* once and returns. Don't return a Promise — TS will reject it. Don't add side-effect import './plugin-exports' lines. If a plugin needs async setup, the consumer awaits it outside the SDK and constructs a sync plugin around the resolved value.
5. Config naming: flatten palette/templates, rename nodes → nodeTypes
Editor.nodes→Editor.nodeTypes(the prop was always node type definitions, not node instances;nodescollided with xyflow's Node and withinitialNodes).createWorkflowBuilder({ palette: { items } })→createWorkflowBuilder({ nodeTypes: [...] })— thepalettewrapper object had a singleitemskey, providing no value. Same fortemplates: { items }→templates: [...].WorkflowBuilderPaletteConfigandWorkflowBuilderTemplatesConfigtypes are gone.
A palette?: {...} namespace can come back later if we ever add palette UI options (ordering, grouping, search) — but it would be a new namespace, not a wrapper for data.
Why: less ceremony, clearer names. nodeTypes matches xyflow's terminology for what these objects are.
How to apply: pass flat arrays to the factory. palette is not a recognized config key.
6. data/palette and data/templates — infrastructure in SDK, defaults in demo (Option B)
The old data/palette.ts mixed two concerns: a zustand-style customPaletteNodes holder with setCustomPaletteNodes + getPaletteData (infrastructure), and a hardcoded fallback array pointing at data/nodes/* demo content. Same for templates.
The split:
- SDK keeps
setCustomPaletteNodes/getPaletteData/setCustomTemplates/getTemplates(state + plugin-decoratable accessors). The fallback array is empty by default. - Demo keeps the actual node definitions (
data/nodes/*), template definitions (data/templates/*), and thin modules (data/palette.ts,data/templates.ts) that exportdemoPaletteItemsanddemoTemplates. The demo passes these intocreateWorkflowBuilderso the editor has real data to show.
Why: the SDK has no business hardcoding a "trigger, action, delay, conditional, decision, notification, aiAgent" palette — that's one particular consumer's content. Forcing consumers to setCustomPaletteNodes(null) to get rid of it was backwards. Option B (throw away the fallback, make the consumer supply node types via config) costs the demo one config line and makes the API honest.
How to apply: if the SDK test harness needs a node definition for validation tests, set it up in beforeAll via setCustomPaletteNodes([...]). Don't add default fallbacks to the SDK's data/ state holders.
7. Build pipeline: rollup-bundled declarations, sideEffects
The previous build:lib did vite build && tsc -p tsconfig.lib.json && cp src/index.d.ts dist/index.d.ts with a hand-maintained src/index.d.ts shim. We now use vite-plugin-dts with rollupTypes: true (which uses rollup-plugin-dts under the hood) to roll up all source types into a single dist/index.d.ts during the normal vite build. The shim is deleted, and so is tsconfig.lib.json.
package.json declares sideEffects for CSS, i18n/plugins-core bootstrap, and plugin-exports files — everything else is pure and can be tree-shaken by consumer bundlers.
Why: the hand-maintained index.d.ts drifts silently when types change in source. Rolling up from source makes drift impossible. Declaring sideEffects keeps consumer bundle size honest.
How to apply: don't hand-write packages/sdk/src/index.d.ts — it doesn't exist any more. Public types are exported from src/**/*.ts and picked up automatically. If you add a file with side effects (CSS import, module-level registration), make sure its glob is in package.json#sideEffects.
Scope boundary — what does NOT live in packages/sdk/
The SDK ships building blocks. It does not ship concrete business content.
- Concrete node implementations (
action,delay,notification,trigger,ai-agent,conditional,decision) live underapps/demo/src/app/data/nodes/. They contain product-specific options ("Hubspot", "CRM System", "Priority: High/Normal/Low") that don't belong in a general-purpose SDK. - Concrete templates live under
apps/demo/src/app/data/templates/. - User-facing plugins (analytics, validation, avoid-nodes-edges, elk-layout, flow-runner, widgets, download-pdf, copy-paste, undo-redo, reshapable-edges, help, __demo) live under
apps/demo/src/app/plugins/. The SDK exposes the registration machinery (registerComponentDecorator,registerFunctionDecorator,registerPluginTranslation) and the slot architecture; plugin implementations are consumer-owned.
Why: an SDK that hardcodes one team's node types, templates, and plugins is not an SDK — it's a product. Keeping these in demo is what lets an external consumer use createWorkflowBuilder({ nodeTypes: [...], plugins: [...] }) with their own content, without forking.
How to apply: new SDK additions must be framework-level (JsonForms renderer infrastructure, xyflow wiring, i18n machinery, schema helpers like getScope / sharedProperties / generalInformation). Anything answering "what does the Workflow Builder workflow look like for our product" goes into demo.
8. Barrel-only public API — plugins and demo consume only @workflowbuilder/sdk
Follow-up of section 1 + PR feedback. Before this change, the SDK's package.json exports declared a wildcard subpath:
"./*": "./src/*"
…which let monorepo consumers reach into SDK internals via @workflowbuilder/sdk/features/diagram/edges/enhanced-base-edge/enhanced-base-edge and similar deep paths. Plus a matching tsconfig paths entry and a Vite alias. Plugins used this heavily (200+ deep imports across apps/demo/src/app/plugins/), and so did apps/demo/src/app/data/nodes/*.
This worked inside the monorepo (tsconfig paths + Vite alias resolved everything), but broke every commercial delivery path:
- Plugin source code sold to customers — when a customer pastes plugin source into their own app and installs only
@workflowbuilder/sdk, deep subpath imports hit SDK raw.ts/.tsxfiles. Those files reference other SDK internals (originally via@/alias), reference CSS modules, reach into features that depend on internal store slices — none of which the customer's bundler can untangle without SDK's own Vite alias setup. - Package distribution — a future
@workflow-builder/pluginspackage would have to declare its peer contract against moving targets (paths likefeatures/changes-tracker/stores/use-changes-tracker-storeare not SemVer-stable; any SDK refactor renames them).
The fix is barrel-only consumption. @workflowbuilder/sdk is now the single consumer entry point for everything plugins and demo-content need. The curated barrel exposes ~100 public symbols organized into categories (Plugin API, UI components, hooks, store access, diagram listeners, JsonForms helpers, utilities, constants, icons).
Changes:
- SDK barrel expanded (packages/sdk/src/index.ts) with all plugin-used APIs: components (
DiagramContainer,LabelEdge,EnhancedBaseEdge,PropertiesBar,ProjectSelection,NodeSection,OptionalNodeContent,SyntaxHighlighterLazy,SelfConnectingEdge,EdgeLabel,FormControlWithLabel), hooks (useFitView,useKeyPress,useEffectChange,useChangesTrackerStore,trackFutureChange,useLabelEdgeHover,useSingleSelectedElement), store access (useStore, store selectors/setters,openModal), listeners (addNodeChangedListener& siblings,addNodeDragStartListener& siblings), helpers (getHandleId,getScope,generalInformation,statusOptions,sharedProperties,globalControls,noop,openInNewTab), constants (EDGE_CURVE_RADIUS,EDGE_OFFSET,SELF_CONNECTING_EDGE_LABEL_OFFSET,VARIABLE_NODES_KEY), types (NodeTypeenum,Option,IfThenElseSchema,DynamicCondition,ComparisonOperator,DecisionNodeSchema,AiAgentNodeSchema,WorkflowNodeTemplateProps,PropertiesBarProps,TranslationKey,NodeData,NodeSchema,UISchema,NodeDataProperties,DiagramModel,IconType,PaletteItem,TemplateModel,DeepPartial,Prettify), and two aliased schemas (decisionSchema,aiAgentSchema). - Demo rewrite — 204 deep imports in
apps/demo/src/app/plugins/and 64 inapps/demo/src/app/data/replaced with barrel imports. Duplicate barrel imports merged (67 files in plugins + 16 in data). - Escape hatches removed —
./*: "./src/*"dropped frompackages/sdk/package.jsonexports,@workflowbuilder/sdk/*dropped fromtsconfig.base.jsonpaths, and the^@workflowbuilder/sdk/(.+)$regex alias dropped fromapps/demo/vite.config.mts. A monorepo developer who accidentally writes a subpath import now gets an immediate type/resolve error — parity with external consumers. - SDK-internal import convention — the public escape hatches above are removed; SDK internals are not reachable from outside the package. Inside, source uses the
@/*alias (defined inpackages/sdk/tsconfig.jsonpaths andpackages/sdk/vite.config.mts) for cross-tree imports and relative paths for siblings. The alias is package-scoped and does not leak to consumers —vite-plugin-dtswithrollupTypes: trueinlines all internal types intodist/index.d.ts. ESLint blocks thesrc/*import pattern (whichtscaccepts via baseUrl but Vite rejects, so catching it at lint time avoids broken builds). New SDK code uses@/for cross-tree imports; existing relative imports stay as-is and migrate opportunistically. - Icons bundled into SDK —
Icon+WBIconre-exported from SDK barrel,vite-plugin-dtsconfigured withbundledPackages: ['@workflow-builder/icons']so the rolled-updist/index.d.tsinlines the icon types (no residualimport from '@workflow-builder/icons').apps/iconsemits proper.d.tsviatscin its build script so rollup-dts has clean declarations to consume. Consumers don't need to install@workflow-builder/iconsseparately. - Explicit type imports enforced — demo's
tsconfig.jsongotverbatimModuleSyntax: trueandeslint.config.mjsgained@typescript-eslint/consistent-type-importswithfixStyle: 'inline-type-imports'. Without this, a plugin file withimport { WorkflowBuilderEdge }would emit a runtime import — fine when the consumer's bundler elides types, broken when it doesn't (verbatim resolution, some Webpack configs, older TS). ESLint autofix cleaned 260 type imports in one pass. - Orphan plugin-only deps removed from SDK —
elkjs,html-to-image,jspdf,libavoid-js,web-workermoved out ofpackages/sdk/package.json(demo already declared them).react-mentions-tsremoved from demo (only SDK uses it). SDK's runtime deps now only reflect what SDK source actually imports.
Why: the business model is "plugin sold as source code" + "plugin sold as package". Both paths require the plugin's imports to be resolvable in any consumer app that installs only @workflowbuilder/sdk. Internal subpath imports made this impossible. Post-refactor, a plugin file can be pasted into any React app with a @workflowbuilder/sdk install and compile cleanly.
How to apply: new plugin code — whether in apps/demo/src/app/plugins/ or (eventually) in a dedicated packages/workflow-builder-plugins/ — imports only from @workflowbuilder/sdk. If a plugin needs something that isn't exported, add it to the barrel deliberately in packages/sdk/src/index.ts (and write down the reasoning in the commit). No deep imports. No @workflowbuilder/sdk/features/... loopholes. verbatimModuleSyntax catches accidental value-imports of types at typecheck time.
9. Post-migration fixes surfaced by consumer testing
Two regressions and one gap became visible only once an external consumer app actually embedded the SDK (pasted plugin source + installed @workflowbuilder/sdk via local path). Fixing each here rather than letting them propagate to every consumer setup.
-
i18n regression (Phase 3a side-effect). Plugins used to register translations via module-load side effects, which populated the shared
pluginsResourcebeforei18n.init()read it. Phase 3a moved plugin registration into thecreateWorkflowBuilder({ plugins: [...] })factory call, which executes after the SDK module graph has initialized i18next — soregisterPluginTranslationupdates a module-level object that i18next no longer consults. Plugin keys (plugins.flowRunner.groupFlow,plugins.help.helpSupport, etc.) surfaced as raw strings in the UI. Fix:registerPluginTranslationnow callsi18n.addResourceBundle(lang, 'translation', { plugins: ... }, true, true)in addition to updatingpluginsResource. Works regardless of when plugins register relative to i18next init. -
CSS incompleteness — SDK's
index.csswas a partial copy of demo'sglobal.css. Missingbody { margin: 0; background-color: var(--wb-background-color); overflow: hidden; }rule left consumer apps with a white canvas instead of the tokenized grey background. Missing@layer ui { @layer base, component; }sub-layer declaration. Missing Google Fonts@importfor Poppins left consumers withsans-seriffallback (since SDK's font stack was'Poppins', sans-serif). All three folded intopackages/sdk/src/index.css. The Poppins@importplus a widened fallback chain (system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif) means consumers get a correct render without wiring up Google Fonts themselves. -
CSS
@importordering. CSS spec requires@importbefore every other statement except@charsetand empty@layerdeclarations. The sub-layer block (@layer ui { @layer base, component; }) has a body and therefore counts as a statement — it must come after all@imports. PostCSS flagged this in demo's build. Reordered.
How to apply: when extending i18n or CSS from the SDK, remember that i18n plugin registration is now runtime-safe (via addResourceBundle), and that packages/sdk/src/index.css is the single source of truth for the library CSS bundle — demo's global.css is redundant and stays only because it's imported by main.tsx alongside the SDK's CSS.
10. Code-review follow-ups — Editor side effects and apps/docs stale refs
Review of the branch surfaced two issues that were not caught during the section 1–9 migration.
-
Editorwas mutating SDK module state fromuseMemo.setCustomPaletteNodes(nodeTypes ?? null)andsetCustomTemplates(templates ?? null)were wrapped inuseMemo([nodeTypes])/useMemo([templates]).useMemois a memoisation primitive — React explicitly reserves the right to skip the callback (error-boundary remount, Strict Mode double-invocation, future compiler optimisations), which would leave the palette/template holders out of sync with props. Replaced with unconditional calls at the top of the render. The setters are idempotent and the state is module-level (not React state), so calling on every render is cheap and correct.useEffectis the wrong fix here — it runs after commit, which would leave the subtree rendering against stale data on the first paint. -
apps/docsstill pointed at deleted source paths. Three Vite aliases inapps/docs/astro.config.mjsresolved againstapps/frontendandapps/types, both removed by sections 1–3.@wb/nodessilently returned an empty glob instead of erroring, so the docs build continued to succeed while every node-reference page (/nodes/action,/nodes/decision, etc.) shipped without its Data Schema / UI Schema tabs. Repointed@wb/nodesatapps/demo/src/app/data/nodes. Dropped@/features/json-form(unused anywhere in docs content) and@workflow-builder/types(the two code-example blocks inadd-custom-node-type.mdxthat referenced it were example consumer imports, not build-time imports, and their intended targets now live in the SDK barrel). Two content files (add-custom-node-type.mdx,properties-sidebar.mdx) and one quick-start (standalone-app.mdx) were updated fromapps/frontend/...paths toapps/demo/...; the@workflow-builder/types/*imports in example code were rewritten to@workflowbuilder/sdk. Top-leveldocs/README.mdanddocs/how-to-change-css-tokens.mdgot matching path updates;docs/using-app-as-component.mddescribes a pre-SDK workflow that no longer applies and got a deprecation banner pointing at the SDK quick-start.
Why: useMemo for side effects is a correctness bug waiting to trigger — the fact that it happened to work in development doesn't make it safe. The docs build passing while silently losing half the /nodes/ page content is worse than a build failure; a failing build would have been caught in CI.
How to apply: never use useMemo/useCallback for side effects. If the state lives outside React (module-level, singleton, etc.), call the setter during render — it's synchronous, ordered before children render, and tolerates Strict Mode double-invocation as long as it's idempotent. When renaming or removing a workspace package, grep for its name across apps/docs/astro.config.mjs, *.mdx, and top-level docs/ — Vite aliases don't fail loudly, and content files aren't typechecked.
11. Quick-fix round — public API hygiene
Smaller follow-ups from the next review pass. None are architectural; together they tighten the public surface against the same scope-boundary rule (§ "Scope boundary — what does NOT live in packages/sdk/") and the project's no-default-export convention.
-
Concrete schemas left the SDK barrel.
decisionSchema/aiAgentSchema(and theirDecisionNodeSchema/AiAgentNodeSchematypes) were exported frompackages/sdk/src/index.tsand the source files lived underpackages/sdk/src/features/. Both schemas embed product-specific content —decisionBrancheswithcomparisonOperatorenums,aiAgentwithchatModel/tools/memoryshaped to the demo's AI tooling vocabulary. Per § 6 / § "Scope boundary" that'sapps/democontent, not building-block content. Schemas moved toapps/demo/src/app/data/nodes/{decision,ai-agent}/schema.ts(with thechatModel/memoryOption[]arrays inlined locally so the demo schema is self-contained), barrel exports dropped, and the two demo files that previously importeddecisionSchema as schema/aiAgentSchema as schemafrom the barrel now import from the colocated./schemamodule. The two flow-runner consumers (plugins/flow-runner/.../decision.ts,decision.spec.ts) that needed theDecisionNodeSchematype forNodeDataProperties<…>typing were repointed at the same colocated source. The SDK still ships theDecisionNodeContainer/AiNodeContainerrenderers that bind these node types — they're an existing deeper leak (§6 says concrete node implementations belong in demo) but moving the rendering layer too is a larger refactor and out of scope for this round; for typing they now use locally-declared structural property types so they no longer depend on the (now-demo) schemas. The single SDK-internalAiAgentTooltype, used by the AI tools control infrastructure, was promoted fromNonNullable<NodeDataProperties<AiAgentNodeSchema>['tools']>[number]to a hand-written object type with the same field shape — the AI tools control is still concrete content but at least it no longer reaches across module boundaries to derive its row type from a schema that no longer lives in this package. -
Default exports removed. Repo convention (
frontend.md) bansexport defaultoutside*.config.*. Three violations:packages/sdk/src/hooks/use-effect-change.ts(default function),packages/sdk/src/store/store.ts(defaultuseStore, with 26 internal call-sites also doingimport useStore from '…'), andpackages/sdk/src/features/plugins-core/utils/missing-plugin.stub.ts(a redundantexport default {}next to the actualexport const plugin). All three converted to named exports; barrel re-exports rewritten fromexport { default as X }toexport { X }; the stub'sexport default {}deleted. Internalimport useStore from …sites converted en-masse viased. -
@xyflow/reactpeer-dep range tightened.>=12.0.0accepted any future major; replaced with^12.0.0to keep semver-major changes from auto-installing into consumer apps without us having validated the SDK against them. -
CSS body reset documented.
packages/sdk/src/index.cssresetsbody(margin: 0,background-color: var(--wb-background-color),overflow: hidden) inside@layer reset— the lowest-precedence layer in the SDK's cascade, so consumer rules win without!important. The behaviour is still a surprise to a consumer who importsstyle.css, especially theoverflow: hidden(which prevents page-level scrolling outside the editor). Added apackages/sdk/README.mddocumenting the resets, what they do, and how to override. The README is intentionally minimal — broader consumer docs (install / usage / screenshots) ride on the npm-publish prep that's still deferred.
Why: the schema move closes the gap left after § 8 — once the public barrel is curated, anything still in it has to clear the same scope-boundary bar. The default-export cleanup is hygiene against a lint convention that wasn't enforced as a rule. The peer-dep tightening protects consumers against drive-by majors. The README closes the most likely "I imported your CSS and now my page can't scroll" support ticket.
How to apply: before adding a new export to src/index.ts, ask whether the symbol is a building block or demo content; if it parameterises on a concrete product enum (status names, model names, tool names, branch shapes), it belongs in apps/demo. New SDK source files use named exports; the lint config catches export default. Peer-dep ranges in packages/sdk/package.json use caret-ranges, never open-ended >=.
Deferred
-
Paid-plugins package. Extracting
apps/demo/src/app/plugins/*(except__demoandhelp, which stay as community content) intopackages/workflow-builder-plugins/is part of the original meeting plan. Section 8 above makes this almost mechanical — every plugin now imports only from@workflowbuilder/sdk, so extraction is a directory move plus apackage.jsonthat declares SDK as peer. When we do extract, the demo will still compose them viacreateWorkflowBuilder({ plugins: [...] })— the only change is the import origin. -
Plugin WASM/worker asset handling.
avoid-nodes-edgesdepends onlibavoid-jswhich ships a WASM binary (libavoid-js/dist/libavoid.wasm) that is not exposed via the package'sexportsfield — bundlers can'timportit as an asset URL. Currently the plugin callsAvoidLib.load('./libavoid.wasm')and relies on a monorepo-onlyfix-worker.tsscript that copies the WASM todist/apps/demo/assets/after build. External consumers (source-code distribution) need a bundler-specific asset-copy step or awindow.__LIBAVOID_WASM_URL__override mechanism. Not architectural — isolated to one plugin, resolvable via documentation or a per-consumer Vite plugin. -
Community-build manifest merge. The
chore/community-build-manifestbranch already has the manifest-based whitelist. Merging it is deferred until this restructuring is fully onmasterso the manifest lands with correctpackages/sdkandapps/demopaths and doesn't have to track a moving target. -
Typedoc API Reference. Now that types roll up from source, typedoc has a clean barrel to consume. Not blocking the restructuring.